diff --git a/.all-contributorsrc b/.all-contributorsrc index ced3b155b..a2d13c2fc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,6 +1,7 @@ { "files": [ - "website/src/pages/credits.mdx" + "website/src/pages/credits.mdx", + "docs/src/assets/contributors.html" ], "imageSize": 75, "commit": false, diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..5edf609fa --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,71 @@ +language: en-US +tone_instructions: '' +early_access: false +enable_free_tier: true +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + auto_title_placeholder: '@coderabbitai' + review_status: true + poem: true + collapse_walkthrough: false + sequence_diagrams: true + path_filters: [] + path_instructions: [] + abort_on_close: true + auto_review: + enabled: true + auto_incremental_review: true + ignore_title_keywords: [] + labels: [] + drafts: false + base_branches: ['v3-alpha', 'master'] + tools: + shellcheck: + enabled: true + ruff: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + biome: + enabled: true + hadolint: + enabled: true + swiftlint: + enabled: true + phpstan: + enabled: true + level: default + golangci-lint: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: + enabled: true + eslint: + enabled: true +chat: + auto_reply: true +knowledge_base: + opt_out: false + learnings: + scope: auto + issues: + scope: auto + jira: + project_keys: [] + linear: + team_keys: [] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 84b7cb6dc..c8ace9747 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,6 +7,8 @@ body: - type: markdown attributes: value: | + ***Please note: No bug reports are currently being accepted for Wails v3*** + ***Please note: No bug reports are currently being accepted for Wails v3*** ***Please note: No bug reports are currently being accepted for Wails v3*** Before submitting this issue, please do the following: - Do a web search for your error. This usually leads to a much better understanding of the issue. diff --git a/.github/file-labeler.yml b/.github/file-labeler.yml new file mode 100644 index 000000000..69494cbae --- /dev/null +++ b/.github/file-labeler.yml @@ -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/**/*' + diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml new file mode 100644 index 000000000..0a7949051 --- /dev/null +++ b/.github/issue-labeler.yml @@ -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' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bf3d8de39..9f8d049ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,20 @@ # Description @@ -14,7 +25,7 @@ Fixes # (issue) ## Type of change -Please delete options that are not relevant. +Please select the option that is relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) @@ -28,6 +39,8 @@ Please describe the tests that you ran to verify your changes. Provide instructi - [ ] Windows - [ ] macOS - [ ] Linux + +If you checked Linux, please specify the distro and version. ## Test Configuration @@ -35,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 diff --git a/.github/stale.yml b/.github/stale.yml index 805bd589d..d8bcc83ec 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -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" diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml new file mode 100644 index 000000000..097eba533 --- /dev/null +++ b/.github/workflows/auto-label-issues.yml @@ -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 diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml new file mode 100644 index 000000000..e88f54eb9 --- /dev/null +++ b/.github/workflows/automated-releases.yml @@ -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<> $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 " + + # 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<> $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 " + + # 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 \ No newline at end of file diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml new file mode 100644 index 000000000..a1b482e11 --- /dev/null +++ b/.github/workflows/build-and-test-v3.yml @@ -0,0 +1,256 @@ +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: + - name: Check if PR is approved + id: check + run: | + if [[ "${{ github.event.review.state }}" == "approved" || "${{ github.event.pull_request.approved }}" == "true" ]]; then + echo "approved=true" >> $GITHUB_OUTPUT + else + echo "approved=false" >> $GITHUB_OUTPUT + fi + + 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] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + 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 + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - 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: [windows-latest, ubuntu-latest, macos-latest] + go-version: [1.24] + + 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 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: Install Task + uses: arduino/setup-task@v2 + with: + 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 + 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: 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: | + task install + wails3 doctor + + - name: Generate template '${{ matrix.template }}' + run: | + mkdir -p ./test-${{ matrix.template }} + cd ./test-${{ matrix.template }} + wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }} + cd ${{ matrix.template }} + 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 \ No newline at end of file diff --git a/.github/workflows/changelog-validation-v3.yml b/.github/workflows/changelog-validation-v3.yml new file mode 100644 index 000000000..8d4b97726 --- /dev/null +++ b/.github/workflows/changelog-validation-v3.yml @@ -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 + }); + diff --git a/.github/workflows/generate-sponsor-image.yml b/.github/workflows/generate-sponsor-image.yml index b8bb0c4ac..585d7e19f 100644 --- a/.github/workflows/generate-sponsor-image.yml +++ b/.github/workflows/generate-sponsor-image.yml @@ -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 diff --git a/.github/workflows/issue-triage-automation.yml b/.github/workflows/issue-triage-automation.yml new file mode 100644 index 000000000..4a827d527 --- /dev/null +++ b/.github/workflows/issue-triage-automation.yml @@ -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'); diff --git a/.github/workflows/nightly-release-v3.yml b/.github/workflows/nightly-release-v3.yml new file mode 100644 index 000000000..ce1c512ed --- /dev/null +++ b/.github/workflows/nightly-release-v3.yml @@ -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<> $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<> $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<> $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<> $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<> $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<> $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 \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr-master.yml similarity index 66% rename from .github/workflows/pr.yml rename to .github/workflows/pr-master.yml index c70050276..1f9fe5622 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr-master.yml @@ -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 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 000000000..88517c46c --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,114 @@ +on: + push: + branches: ['v3-alpha'] + workflow_dispatch: + +concurrency: + group: publish-npm-v3 + cancel-in-progress: true + +jobs: + 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: + - 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: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - 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 + run: | + git add . + git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}" + git push + fi + + - name: Publish npm package + uses: JS-DevTools/npm-publish@v3 + with: + package: v3/internal/runtime/desktop/@wailsio/runtime + access: public + token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 453e4cb85..a59818660 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -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: diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml new file mode 100644 index 000000000..c4ffd25fe --- /dev/null +++ b/.github/workflows/stale-issues.yml @@ -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 diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml new file mode 100644 index 000000000..63df09935 --- /dev/null +++ b/.github/workflows/test-nightly-releases.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/test-simple.yml b/.github/workflows/test-simple.yml new file mode 100644 index 000000000..8c4c88295 --- /dev/null +++ b/.github/workflows/test-simple.yml @@ -0,0 +1,11 @@ +name: Test Simple + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Test + run: echo "Hello World" \ No newline at end of file diff --git a/.github/workflows/upload-source-documents.yml b/.github/workflows/upload-source-documents.yml index df15246fc..69d6c3e48 100644 --- a/.github/workflows/upload-source-documents.yml +++ b/.github/workflows/upload-source-documents.yml @@ -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 diff --git a/.github/workflows/v3-docs.yml b/.github/workflows/v3-docs.yml new file mode 100644 index 000000000..7552d43ab --- /dev/null +++ b/.github/workflows/v3-docs.yml @@ -0,0 +1,42 @@ +name: Deploy to GitHub Pages + +on: + # Trigger the workflow every time you push to the `main` branch + # Using a different branch name? Replace `main` with your branchโ€™s name + push: + branches: [v3-alpha] + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + +# Allow this job to clone the repo and create a page deployment +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - name: Checkout your repository using git + uses: actions/checkout@v4 + - name: Install, build, and upload your site output + uses: withastro/action@v2 + with: + path: docs + node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 18. (optional) + # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e7888b44a..10709495a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +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 -/mkdocs-website/site +/docs/site +.aider* diff --git a/.prettierrc.yml b/.prettierrc.yml index 685d8b6e7..1e2d2dada 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -1,6 +1,7 @@ overrides: - files: - "**/*.md" + - "**/*.mdx" options: printWidth: 80 proseWrap: always diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml index 7cc165825..051da3e42 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -16,6 +16,11 @@ includes: dir: v3 optional: true + docs: + taskfile: docs + dir: docs + optional: true + tasks: contributors:check: cmds: diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..6240da8b1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/docs/.npmrc b/docs/.npmrc new file mode 100644 index 000000000..cffe8cdef --- /dev/null +++ b/docs/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json new file mode 100644 index 000000000..22a15055d --- /dev/null +++ b/docs/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/docs/.vscode/launch.json b/docs/.vscode/launch.json new file mode 100644 index 000000000..d64220976 --- /dev/null +++ b/docs/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..cf70888f2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,60 @@ +# Starlight Starter Kit: Basics + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +```sh +npm create astro@latest -- --template starlight +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics) +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs) + +> ๐Ÿง‘โ€๐Ÿš€ **Seasoned astronaut?** Delete this file. Have fun! + +## ๐Ÿš€ Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and +files: + +```sh +. +โ”œโ”€โ”€ public/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ assets/ +โ”‚ โ”œโ”€โ”€ content/ +โ”‚ โ”‚ โ”œโ”€โ”€ docs/ +โ”‚ โ”‚ โ””โ”€โ”€ config.ts +โ”‚ โ””โ”€โ”€ env.d.ts +โ”œโ”€โ”€ astro.config.mjs +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. +Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative +link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## ๐Ÿงž Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## ๐Ÿ‘€ Want to learn more? + +Check out [Starlightโ€™s docs](https://starlight.astro.build/), read +[the Astro documentation](https://docs.astro.build), or jump into the +[Astro Discord server](https://astro.build/chat). diff --git a/docs/Taskfile.yml b/docs/Taskfile.yml new file mode 100644 index 000000000..76515fcd8 --- /dev/null +++ b/docs/Taskfile.yml @@ -0,0 +1,30 @@ +# https://taskfile.dev + +version: '3' + +tasks: + + setup: + summary: Setup the project + preconditions: + - sh: npm --version + msg: "Looks like npm isn't installed." + cmds: + - npm install + + dev: + summary: Run the dev server + preconditions: + - sh: npm --version + msg: "Looks like npm isn't installed." + cmds: + - npm run dev + + build: + summary: Build the docs + preconditions: + - sh: npm --version + msg: "Looks like npm isn't installed." + cmds: + - npm run build + diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 000000000..00285d9be --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,145 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import sitemap from "@astrojs/sitemap"; +import starlightLinksValidator from "starlight-links-validator"; +import starlightImageZoom from "starlight-image-zoom"; +import starlightBlog from "starlight-blog"; +import { authors } from "./src/content/authors"; + +// https://astro.build/config +export default defineConfig({ + // TODO: update this + site: "https://wails.io", + trailingSlash: "ignore", + compressHTML: true, + output: "static", + build: { format: "directory" }, + devToolbar: { enabled: true }, + integrations: [ + sitemap(), + starlight({ + title: "", + // If a title is added, also update the delimiter. + titleDelimiter: "", + logo: { + dark: "./src/assets/wails-logo-horizontal-dark.svg", + light: "./src/assets/wails-logo-horizontal-light.svg", + }, + favicon: "./public/favicon.svg", + description: "Build desktop applications using Go & Web Technologies.", + pagefind: true, + customCss: ["./src/stylesheets/extra.css"], + lastUpdated: true, // Note, this needs git clone with fetch depth 0 to work + pagination: true, + editLink: { + // TODO: update this + baseUrl: "https://github.com/wailsapp/wails/edit/v3-alpha/docs", + }, + social: { + github: "https://github.com/wailsapp/wails", + discord: "https://discord.gg/JDdSxwjhGf", + "x.com": "https://x.com/wailsapp", + }, + defaultLocale: "root", + locales: { + root: { label: "English", lang: "en", dir: "ltr" }, + // Example of how a new language is added. + // After this, you create a directory named after the language inside content/docs/ + // with the same structure as the root language + // eg content/docs/gr/changelog.md or content/docs/gr/api/application.mdx + // gr: { label: "Greek", lang: "el", dir: "ltr" }, + }, + plugins: [ + // https://starlight-links-validator.vercel.app/configuration/ + // starlightLinksValidator({ + // exclude: [ + // // TODO: Fix these links in the blog/wails-v2-released file + // // "/docs/reference/options#theme", + // // "/docs/reference/options#customtheme", + // // "/docs/guides/application-development#application-menu", + // // "/docs/reference/runtime/dialog", + // // "/docs/reference/options#windowistranslucent", + // // "/docs/reference/options#windowistranslucent-1", + // // "/docs/guides/windows-installer", + // // "/docs/reference/runtime/intro", + // // "/docs/guides/obfuscated", + // // "/docs/howdoesitwork#calling-bound-go-methods", + // ], + // }), + // https://starlight-image-zoom.vercel.app/configuration/ + starlightImageZoom(), + // https://starlight-blog-docs.vercel.app/configuration + starlightBlog({ + title: "Wails Blog", + authors: authors, + }), + ], + sidebar: [ + { label: "Home", link: "/" }, + { + label: "Getting Started", + autogenerate: { directory: "getting-started", collapsed: false }, + }, + { + label: "Tutorials", + collapsed: true, + autogenerate: { directory: "tutorials", collapsed: true }, + }, + { + label: "What's New", + link: "/whats-new", + badge: { text: "New", variant: "tip" }, + }, + { label: "v3 Alpha Feedback", link: "/feedback" }, + { + label: "Learn", + collapsed: true, + autogenerate: { directory: "learn", collapsed: true }, + }, + { + label: "Guides", + collapsed: true, + autogenerate: { directory: "guides", collapsed: true }, + }, + // { + // label: "API", + // collapsed: true, + // autogenerate: { directory: "api", collapsed: true }, + // }, + { + label: "Community", + collapsed: true, + items: [ + { label: "Links", link: "/community/links" }, + { label: "Templates", link: "/community/templates" }, + { + label: "Showcase", + autogenerate: { + directory: "community/showcase", + collapsed: true, + }, + }, + ], + }, + // { + // label: "Development", + // collapsed: true, + // autogenerate: { directory: "development", collapsed: true }, + // }, + { label: "Status", link: "/status" }, + { label: "Changelog", link: "/changelog" }, + { + label: "Sponsor", + link: "https://github.com/sponsors/leaanthony", + badge: { text: "โค๏ธ" }, + }, + { + label: "Credits", + link: "/credits", + badge: { text: "๐Ÿ‘‘" }, + }, + ], + }), + ], +}); diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 000000000..6d5c5f596 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,9980 @@ +{ + "name": "wails-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wails-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/check": "0.9.4", + "@astrojs/react": "4.1.0", + "@astrojs/starlight": "0.29.2", + "@types/react": "19.0.1", + "@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", + "sharp": "0.33.5", + "starlight-blog": "0.15.0", + "starlight-image-zoom": "0.9.0", + "starlight-links-validator": "0.13.4", + "starlight-showcases": "0.2.0", + "typescript": "5.7.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@astro-community/astro-embed-twitter": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-twitter/-/astro-embed-twitter-0.5.8.tgz", + "integrity": "sha512-O2ptQPw+DfipukK8czjJcTcyVgDsrs3OmrHbc3YmWRglaUTOpSTImzPo076POyNBSWjLaRKloul81DFiAMNjTA==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-utils": "^0.1.0" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astro-community/astro-embed-utils": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-utils/-/astro-embed-utils-0.1.3.tgz", + "integrity": "sha512-eiMO+vfCdE9GtW6qE7X5Xl6YCKZDCoXJEWqRofQcoC3GHjqN2/WhJlnaxNVRq3demSO03UNtho57Em5p7o7AOA==", + "license": "MIT", + "dependencies": { + "linkedom": "^0.14.26" + } + }, + "node_modules/@astro-community/astro-embed-youtube": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-youtube/-/astro-embed-youtube-0.5.6.tgz", + "integrity": "sha512-/mRfCl/eTBUz0kmjD1psOy0qoDDBorVp0QumUacjFcIkBullYtbeFQ2ZGZ+3N/tA6cR/OIyzr2QA4dQXlY6USg==", + "license": "MIT", + "dependencies": { + "lite-youtube-embed": "^0.3.3" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astrojs/check": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.4.tgz", + "integrity": "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==", + "license": "MIT", + "dependencies": { + "@astrojs/language-server": "^2.15.0", + "chokidar": "^4.0.1", + "kleur": "^4.1.5", + "yargs": "^17.7.2" + }, + "bin": { + "astro-check": "dist/bin.js" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.10.3.tgz", + "integrity": "sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.4.1.tgz", + "integrity": "sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g==", + "license": "MIT" + }, + "node_modules/@astrojs/language-server": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.15.4.tgz", + "integrity": "sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.10.3", + "@astrojs/yaml2ts": "^0.2.2", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@volar/kit": "~2.4.7", + "@volar/language-core": "~2.4.7", + "@volar/language-server": "~2.4.7", + "@volar/language-service": "~2.4.7", + "fast-glob": "^3.2.12", + "muggle-string": "^0.4.1", + "volar-service-css": "0.0.62", + "volar-service-emmet": "0.0.62", + "volar-service-html": "0.0.62", + "volar-service-prettier": "0.0.62", + "volar-service-typescript": "0.0.62", + "volar-service-typescript-twoslash-queries": "0.0.62", + "volar-service-yaml": "0.0.62", + "vscode-html-languageservice": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz", + "integrity": "sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg==", + "license": "MIT", + "dependencies": { + "@astrojs/prism": "3.1.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.1", + "remark-smartypants": "^3.0.2", + "shiki": "^1.22.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-3.1.9.tgz", + "integrity": "sha512-3jPD4Bff6lIA20RQoonnZkRtZ9T3i0HFm6fcDF7BMsKIZ+xBP2KXzQWiuGu62lrVCmU612N+SQVGl5e0fI+zWg==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "5.3.0", + "@mdx-js/mdx": "^3.1.0", + "acorn": "^8.14.0", + "es-module-lexer": "^1.5.4", + "estree-util-visit": "^2.0.0", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.3", + "kleur": "^4.1.5", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.0", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0" + }, + "peerDependencies": { + "astro": "^4.8.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.1.0.tgz", + "integrity": "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.29.0" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0" + } + }, + "node_modules/@astrojs/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.1.0.tgz", + "integrity": "sha512-8F0ncvcCexVeQZMwPouLSFuzCK1KXUIYQ57lW3ZG2p7B5DGAajXGanb/CGF7MMSpX8Z0t9sELQqLHOCV/+78Ig==", + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "ultrahtml": "^1.5.3", + "vite": "^6.0.1" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", + "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@astrojs/react/node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/@astrojs/react/node_modules/vite": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/@astrojs/rss": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.5.tgz", + "integrity": "sha512-IyJVL6z09AQtxbgLaAwebT3T5YKe4oTHDesqydJv1KLHw+zEzzMCFuuNsEyxjiqu7df9+DDCpDXLj/WRiEUXvw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^4.2.7", + "kleur": "^4.1.5" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.2.1.tgz", + "integrity": "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.29.2.tgz", + "integrity": "sha512-xv9AhWkP3fxCB6EF6MlT4yEbxzye3aMSbuVbFEGbQh8G/w1MPhdNCnQakIHpmIwwyxwG9cW3mQdAZum4oOO39w==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "^3.1.3", + "@astrojs/sitemap": "^3.1.6", + "@pagefind/default-ui": "^1.0.3", + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.38.3", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.0.3", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^4.14.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.1.0.tgz", + "integrity": "sha512-/ca/+D8MIKEC8/A9cSaPUqQNZm+Es/ZinRv0ZAzvu2ios7POQSsVD+VOj7/hypWNsNM3T7RpfgNq7H2TU1KEHA==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.0.0", + "debug": "^4.3.4", + "dlv": "^1.1.3", + "dset": "^3.1.3", + "is-docker": "^3.0.0", + "is-wsl": "^3.0.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz", + "integrity": "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.5.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "license": "MIT" + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-parser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.0.tgz", + "integrity": "sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==", + "license": "MIT", + "dependencies": { + "@emmetio/stream-reader": "^2.2.0", + "@emmetio/stream-reader-utils": "^0.1.0" + } + }, + "node_modules/@emmetio/html-matcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", + "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "license": "ISC", + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", + "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", + "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", + "license": "MIT" + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.38.3.tgz", + "integrity": "sha512-s0/OtdRpBONwcn23O8nVwDNQqpBGKscysejkeBkwlIeHRLZWgiTVrusT5Idrdz1d8cW5wRk9iGsAIQmwDPXgJg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.38.3.tgz", + "integrity": "sha512-qL2oC6FplmHNQfZ8ZkTR64/wKo9x0c8uP2WDftR/ydwN/yhe1ed7ZWYb8r3dezxsls+tDokCnN4zYR594jbpvg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.38.3.tgz", + "integrity": "sha512-kqHnglZeesqG3UKrb6e9Fq5W36AZ05Y9tCREmSN2lw8LVTqENIeCIkLDdWtQ5VoHlKqwUEQFTVlRehdwoY7Gmw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3", + "shiki": "^1.22.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.38.3.tgz", + "integrity": "sha512-dPK3+BVGTbTmGQGU3Fkj3jZ3OltWUAlxetMHI6limUGCWBCucZiwoZeFM/WmqQa71GyKRzhBT+iEov6kkz2xVA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pHnPL2rm4xbe0LqV376g84hUIsVdy4PK6o2ACveo0DSGoC40eOIwPUPftnUPUinSdDWkkySaL5FT5r9hsXk0ZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.2.0.tgz", + "integrity": "sha512-q2tcnfvcRyx0GnrJoUQJ5bRpiFNtI8DZWM6a4/k8sNJxm2dbM1BnY5hUeo4MbDfpb64Qc1wRMcvBUSOaMKBjfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.2.0.tgz", + "integrity": "sha512-MDSbm34veKpzFP5eJMh/pcPdrOc4FZKUsbpDsbdjSLC2ZeuTjsfDBNu9MGZaNUvGKUdlKk5JozQkVO/dzdSxrQ==", + "license": "MIT" + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.2.0.tgz", + "integrity": "sha512-wVtLOlF9AUrwLovP9ZSEKOYnwIVrrxId4I2Mz02Zxm3wbUIJyx8wHf6LyEf7W7mJ6rEjW5jtavKAbngKCAaicg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.2.0.tgz", + "integrity": "sha512-Lo5aO2bA++sQTeEWzK5WKr3KU0yzVH5OnTY88apZfkgL4AVfXckH2mrOU8ouYKCLNPseIYTLFEdj0V5xjHQSwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.2.0.tgz", + "integrity": "sha512-tGQcwQAb5Ndv7woc7lhH9iAdxOnTNsgCz8sEBbsASPB2A0uI8BWBmVdf2GFLQkYHqnnqYuun63sa+UOzB7Ah3g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.24.1.tgz", + "integrity": "sha512-3q/9oarMVcLqJ+NQOdKL40dJVq/UKCsiWXz3QRQPBglHqa8dDJ0p6TuMuk2gHphy5FZcvFtg4UHBgpW0JtZ8+A==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.24.1", + "@shikijs/engine-oniguruma": "1.24.1", + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.24.1.tgz", + "integrity": "sha512-lNgUSHYDYaQ6daj4lJJqcY2Ru9LgHwpFoposJkRVRPh21Yg4kaPFRhzaWoSg3PliwcDOpDuMy3xsmQaJp201Fg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-es": "0.7.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.1.tgz", + "integrity": "sha512-KdrTIBIONWd+Xs61eh8HdIpfigtrseat9dpARvaOe2x0g/FNTbwbkGr3y92VSOVD1XotzEskh3v/nCzyWjkf7g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.1.tgz", + "integrity": "sha512-ZwZFbShFY/APfKNt3s9Gv8rhTm29GodSKsOW66X6N+HGsZuaHalE1VUEX4fv93UXHTZTLjb3uxn63F96RhGfXw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "license": "MIT" + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/picomatch": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz", + "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", + "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@volar/kit": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.10.tgz", + "integrity": "sha512-ul+rLeO9RlFDgkY/FhPWMnpFqAsjvjkKz8VZeOY5YCJMwTblmmSBlNJtFNxSBx9t/k1q80nEthLyxiJ50ZbIAg==", + "license": "MIT", + "dependencies": { + "@volar/language-service": "2.4.10", + "@volar/typescript": "2.4.10", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", + "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.10" + } + }, + "node_modules/@volar/language-server": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.10.tgz", + "integrity": "sha512-odQsgrJh8hOXfxkSj/BSnpjThb2/KDhbxZnG/XAEx6E3QGDQv4hAOz9GWuKoNs0tkjgwphQGIwDMT1JYaTgRJw==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "@volar/language-service": "2.4.10", + "@volar/typescript": "2.4.10", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.10.tgz", + "integrity": "sha512-VxUiWS11rnRzakkqw5x1LPhsz+RBfD0CrrFarLGW2/voliYXEdCuSOM3r8JyNRvMvP4uwhD38ccAdTcULQEAIQ==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", + "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", + "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "license": "MIT", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "4.16.17", + "resolved": "https://registry.npmjs.org/astro/-/astro-4.16.17.tgz", + "integrity": "sha512-OuD+BP7U6OqQLKtZ/FJkU2S+TOlifxS/OKUbZOb5p6y+LLBa1J3zHRJrIl7DUSq6eXY+9wSWwbJpD9JS+lqhxA==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.10.3", + "@astrojs/internal-helpers": "0.4.1", + "@astrojs/markdown-remark": "5.3.0", + "@astrojs/telemetry": "3.1.0", + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/types": "^7.26.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.1.3", + "@types/babel__core": "^7.20.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.1.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^0.7.2", + "cssesc": "^3.0.0", + "debug": "^4.3.7", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.1.1", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.5.4", + "esbuild": "^0.21.5", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.2", + "flattie": "^1.1.1", + "github-slugger": "^2.0.0", + "gray-matter": "^4.0.3", + "html-escaper": "^3.0.3", + "http-cache-semantics": "^4.1.1", + "js-yaml": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.14", + "magicast": "^0.3.5", + "micromatch": "^4.0.8", + "mrmime": "^2.0.0", + "neotraverse": "^0.6.18", + "ora": "^8.1.1", + "p-limit": "^6.1.0", + "p-queue": "^8.0.1", + "preferred-pm": "^4.0.0", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.6.3", + "shiki": "^1.23.1", + "tinyexec": "^0.3.1", + "tsconfck": "^3.1.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3", + "vite": "^5.4.11", + "vitefu": "^1.0.4", + "which-pm": "^3.0.0", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.5", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "optionalDependencies": { + "sharp": "^0.33.3" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.38.3.tgz", + "integrity": "sha512-Tvdc7RV0G92BbtyEOsfJtXU35w41CkM94fOAzxbQP67Wj5jArfserJ321FO4XA7WG9QMV0GIBmQq77NBIRDzpQ==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.38.3" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro-remote": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/astro-remote/-/astro-remote-0.3.2.tgz", + "integrity": "sha512-Xwm6Y+ldQEnDB2l1WwVqeUs3QvUX8LtJWnovpXlf8xhpicPu159jXOhDbHZS9wilGO/+/nR67A1qskF8pDvdGQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0", + "marked": "^12.0.0", + "marked-footnote": "^1.2.2", + "marked-smartypants": "^1.1.6", + "ultrahtml": "^1.5.3" + }, + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.1.tgz", + "integrity": "sha512-dbeqFTLYEwlFg7UGtcZhCCG/2WayX72zK3Sq323CEX29CY81tYfVhw1MIdduCtpstB0cTOhJswWlM/OEB3Xp+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "license": "MIT", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", + "license": "ISC" + }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.38.3.tgz", + "integrity": "sha512-COM04AiUotHCKJgWdn7NtW2lqu8OW8owAidMpkXt1qxrZ9Q2iC7+tok/1qIn2ocGnczvr9paIySgGnEwFeEQ8Q==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3", + "@expressive-code/plugin-frames": "^0.38.3", + "@expressive-code/plugin-shiki": "^0.38.3", + "@expressive-code/plugin-text-markers": "^0.38.3" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/framer-motion": { + "version": "11.14.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.14.4.tgz", + "integrity": "sha512-NQuzr9JbeJDMQmy0FFLhLzk9h1kAjVC1tGE/HY4ubF02B95EBm2lpA21LE3Od/OpXqXgp0zl5Hdqu25hliBRsA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.14.3", + "motion-utils": "^11.14.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", + "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz", + "integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", + "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree/node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-estree/node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", + "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/linkedom": { + "version": "0.14.26", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.26.tgz", + "integrity": "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^8.0.1", + "uhyphen": "^0.2.0" + } + }, + "node_modules/lite-youtube-embed": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.3.tgz", + "integrity": "sha512-gFfVVnj6NRjxVfJKo3qoLtpi0v5mn3AcR4eKD45wrxQuxzveFJUb+7Cr6uV6n+DjO8X3p0UzPPquhGt0H/y+NA==", + "license": "Apache-2.0" + }, + "node_modules/load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/load-yaml-file/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/load-yaml-file/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.14", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", + "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-footnote": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/marked-footnote/-/marked-footnote-1.2.4.tgz", + "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, + "node_modules/marked-plaintify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/marked-plaintify/-/marked-plaintify-1.0.1.tgz", + "integrity": "sha512-KQhxtuVWf3Ij3YMiW4ArlgNOVmzOAlP0o/upsu2+h7Q4TCAwG4UvkYTteZF2sDDomXQnNSLmfyAhoR0gx2Orgw==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, + "node_modules/marked-smartypants": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/marked-smartypants/-/marked-smartypants-1.1.9.tgz", + "integrity": "sha512-VPeuaUr5IWptI7nJdgQ9ugrLWYGv13NdzEXTtKY3cmB4aRWOI2RzhLlf+xQp6Wnob9SAPO2sNVlfSJr+nflk/A==", + "license": "MIT", + "dependencies": { + "smartypants": "^0.2.2" + }, + "peerDependencies": { + "marked": ">=4 <16" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", + "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "10.9.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.3.tgz", + "integrity": "sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", + "cytoscape-cose-bilkent": "^4.1.0", + "d3": "^7.4.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "^3.0.5 <3.1.7", + "elkjs": "^0.9.0", + "katex": "^0.16.9", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/mermaid/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mermaid/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/mermaid/node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mermaid/node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mermaid/node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mermaid/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mermaid/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", + "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", + "license": "MIT", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", + "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/motion": { + "version": "11.14.4", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.14.4.tgz", + "integrity": "sha512-ZIaw6ko88B8rSmBEFzqbTCQMbo9xMu8f4PSXSGdb9DTDy8R0sXcbwMEKmTEYkrj9TmZ4n+Ebd0KYjtqHgzRkRQ==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.14.4", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz", + "integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==", + "license": "MIT" + }, + "node_modules/motion-utils": { + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz", + "integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oniguruma-to-es": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.7.0.tgz", + "integrity": "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg==", + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.0.2", + "regex-recursion": "^4.3.0" + } + }, + "node_modules/ora": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", + "integrity": "sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", + "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.3.tgz", + "integrity": "sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pagefind": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.2.0.tgz", + "integrity": "sha512-sFVv5/x73qCp9KlLHv8/uWDv7rG1tsWcG9MuXc5YTrXIrb8c1Gshm9oc5rMLXNZILXUWai8WczqaK4jjroEzng==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.2.0", + "@pagefind/darwin-x64": "1.2.0", + "@pagefind/linux-arm64": "1.2.0", + "@pagefind/linux-x64": "1.2.0", + "@pagefind/windows-x64": "1.2.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/preferred-pm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-4.0.0.tgz", + "integrity": "sha512-gYBeFTZLu055D8Vv3cSPox/0iTPtkzxpLroSYYA7WXgRi31WCJ51Uyl8ZiPeUUjyvs2MBzK+S8v9JVUgHU/Sqw==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "find-yarn-workspace-root2": "1.2.16", + "which-pm": "^3.0.0" + }, + "engines": { + "node": ">=18.12" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regex": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.0.2.tgz", + "integrity": "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-4.3.0.tgz", + "integrity": "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.38.3.tgz", + "integrity": "sha512-RYSSDkMBikoTbycZPkcWp6ELneANT4eTpND1DSRJ6nI2eVFUwTBDCvE2vO6jOOTaavwnPiydi4i/87NRyjpdOA==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.38.3" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", + "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shiki": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.24.1.tgz", + "integrity": "sha512-/qByWMg05+POb63c/OvnrU17FcCUa34WU4F6FCrd/mjDPEDPl8YUNRkRMbo8l3iYMLydfCgxi1r37JFoSw8A4A==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.24.1", + "@shikijs/engine-javascript": "1.24.1", + "@shikijs/engine-oniguruma": "1.24.1", + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", + "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smartypants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/smartypants/-/smartypants-0.2.2.tgz", + "integrity": "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==", + "license": "BSD-3-Clause", + "bin": { + "smartypants": "bin/smartypants.js", + "smartypantsu": "bin/smartypantsu.js" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/starlight-blog": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/starlight-blog/-/starlight-blog-0.15.0.tgz", + "integrity": "sha512-zNs8Z6eJAqurarD3up8kosfAXNDiZEaweI7S7vRfeTN6eOZijPa3QVifJbYK8n6sdX4W3liEqPkDF+PIz3TxxQ==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "3.1.9", + "@astrojs/rss": "4.0.5", + "astro-remote": "0.3.2", + "github-slugger": "2.0.0", + "marked": "12.0.2", + "marked-plaintify": "1.0.1", + "ultrahtml": "1.5.3" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.28.3" + } + }, + "node_modules/starlight-image-zoom": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/starlight-image-zoom/-/starlight-image-zoom-0.9.0.tgz", + "integrity": "sha512-XG87T80g5hsT6dvtNk9OKx0gD+M8lsloVTApQYnxdc3JD8lQBfu2kCsrwkyrwXZRtV7JRyd0PDHwx1UFmGmI1g==", + "license": "MIT", + "dependencies": { + "rehype-raw": "7.0.0", + "unist-util-visit": "5.0.0", + "unist-util-visit-parents": "6.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.22.0" + } + }, + "node_modules/starlight-links-validator": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/starlight-links-validator/-/starlight-links-validator-0.13.4.tgz", + "integrity": "sha512-LdmLbJyPHVrSUhcuxiP3pJNnW8zRcOg/32C996Ic0LOCKbB8vylqHLvAMdIhT67FvEV4eAROun+2wTVU2J156A==", + "license": "MIT", + "dependencies": { + "@types/picomatch": "2.3.3", + "github-slugger": "2.0.0", + "hast-util-from-html": "2.0.1", + "hast-util-has-property": "3.0.0", + "is-absolute-url": "4.0.1", + "kleur": "4.1.5", + "mdast-util-to-string": "4.0.0", + "picomatch": "4.0.2", + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.15.0", + "astro": ">=4.0.0" + } + }, + "node_modules/starlight-links-validator/node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/starlight-showcases": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/starlight-showcases/-/starlight-showcases-0.2.0.tgz", + "integrity": "sha512-YWJuTqArkUdVJV85VKZJ0BvKCQRu1SKtH/Cr5t6G/oIfI4IptWc92E7BmiuNnpuQ2U7TczTRidCYurPrbgQQVA==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-twitter": "^0.5.4", + "@astro-community/astro-embed-youtube": "^0.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.23.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tsconfck": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz", + "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", + "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.5.tgz", + "integrity": "sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/ultrahtml": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz", + "integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.4.tgz", + "integrity": "sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.62.tgz", + "integrity": "sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==", + "license": "MIT", + "dependencies": { + "vscode-css-languageservice": "^6.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.62.tgz", + "integrity": "sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==", + "license": "MIT", + "dependencies": { + "@emmetio/css-parser": "^0.4.0", + "@emmetio/html-matcher": "^1.3.0", + "@vscode/emmet-helper": "^2.9.3", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.62.tgz", + "integrity": "sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==", + "license": "MIT", + "dependencies": { + "vscode-html-languageservice": "^5.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.62.tgz", + "integrity": "sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.62.tgz", + "integrity": "sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==", + "license": "MIT", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.6.2", + "typescript-auto-import-cache": "^0.3.3", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.62.tgz", + "integrity": "sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-yaml": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.62.tgz", + "integrity": "sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8", + "yaml-language-server": "~1.15.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.2.tgz", + "integrity": "sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.3.1.tgz", + "integrity": "sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", + "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "npm": ">=7.0.0" + } + }, + "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, + "node_modules/which-pm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.0.tgz", + "integrity": "sha512-ysVYmw6+ZBhx3+ZkcPwRuJi38ZOTLJJ33PSHaitLxSKUMsh0LkKd0nC69zZCwt5D+AYUcMK2hhw4yWny20vSGg==", + "license": "MIT", + "dependencies": { + "load-yaml-file": "^0.2.0" + }, + "engines": { + "node": ">=18.12" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yaml-language-server": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.15.0.tgz", + "integrity": "sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "lodash": "4.17.21", + "request-light": "^0.5.7", + "vscode-json-languageservice": "4.1.8", + "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2", + "yaml": "2.2.2" + }, + "bin": { + "yaml-language-server": "bin/yaml-language-server" + }, + "optionalDependencies": { + "prettier": "2.8.7" + } + }, + "node_modules/yaml-language-server/node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/yaml-language-server/node_modules/request-light": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", + "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "license": "MIT", + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..5d10c8ed3 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,31 @@ +{ + "name": "wails-docs", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "0.9.4", + "@astrojs/react": "4.1.0", + "@astrojs/starlight": "0.29.2", + "@types/react": "19.0.1", + "@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", + "sharp": "0.33.5", + "starlight-blog": "0.15.0", + "starlight-image-zoom": "0.9.0", + "starlight-links-validator": "0.13.4", + "starlight-showcases": "0.2.0", + "typescript": "5.7.2" + } +} diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 000000000..e682e4453 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/public/missing.png b/docs/public/missing.png new file mode 100644 index 000000000..05d2bafc3 Binary files /dev/null and b/docs/public/missing.png differ diff --git a/docs/public/sponsors/jetbrains-grayscale.webp b/docs/public/sponsors/jetbrains-grayscale.webp new file mode 100644 index 000000000..be39c4856 Binary files /dev/null and b/docs/public/sponsors/jetbrains-grayscale.webp differ diff --git a/docs/public/sponsors/sponsors.svg b/docs/public/sponsors/sponsors.svg new file mode 100644 index 000000000..1a9985e59 --- /dev/null +++ b/docs/public/sponsors/sponsors.svg @@ -0,0 +1,197 @@ + + + + +Champion + Masato Miura + +Bronze Sponsors + Cody Bentley + + + + Kazuya Gokita + + + + Simon Thomas + + + + CodeRabbit + +Covering Costs + Nick + + + + Marcus + + + + John + + + + Matt Holt + + + + Iain + + + + Julien + + + + Andrei + + + + Michael + +Buying Breakfast + tc-hib + + + + Tai Groot + + + + Tom Wu + + + + Arden + + + + igops + + + + vaaski + +Buying Coffee + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Helpers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/sponsors/zsa.png b/docs/public/sponsors/zsa.png new file mode 100644 index 000000000..507e983fa Binary files /dev/null and b/docs/public/sponsors/zsa.png differ diff --git a/docs/src/assets/blog-images/browser.webp b/docs/src/assets/blog-images/browser.webp new file mode 100644 index 000000000..a19d5b036 Binary files /dev/null and b/docs/src/assets/blog-images/browser.webp differ diff --git a/docs/src/assets/blog-images/build-cross-windows.webp b/docs/src/assets/blog-images/build-cross-windows.webp new file mode 100644 index 000000000..ba1a538f6 Binary files /dev/null and b/docs/src/assets/blog-images/build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-amd.webp b/docs/src/assets/blog-images/build-darwin-amd.webp new file mode 100644 index 000000000..6126129ca Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-amd.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-arm.webp b/docs/src/assets/blog-images/build-darwin-arm.webp new file mode 100644 index 000000000..47fcf4583 Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-arm.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-universal.webp b/docs/src/assets/blog-images/build-darwin-universal.webp new file mode 100644 index 000000000..c95d92ccb Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-universal.webp differ diff --git a/docs/src/assets/blog-images/devtools.png b/docs/src/assets/blog-images/devtools.png new file mode 100644 index 000000000..e56a0d304 Binary files /dev/null and b/docs/src/assets/blog-images/devtools.png differ diff --git a/docs/src/assets/blog-images/linux-build-cross-windows.webp b/docs/src/assets/blog-images/linux-build-cross-windows.webp new file mode 100644 index 000000000..cbed7585b Binary files /dev/null and b/docs/src/assets/blog-images/linux-build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/montage.png b/docs/src/assets/blog-images/montage.png new file mode 100644 index 000000000..ddd771851 Binary files /dev/null and b/docs/src/assets/blog-images/montage.png differ diff --git a/docs/src/assets/blog-images/multiwindow.webp b/docs/src/assets/blog-images/multiwindow.webp new file mode 100644 index 000000000..746a1d7f2 Binary files /dev/null and b/docs/src/assets/blog-images/multiwindow.webp differ diff --git a/docs/src/assets/blog-images/remote-linux.webp b/docs/src/assets/blog-images/remote-linux.webp new file mode 100644 index 000000000..25ad11ea3 Binary files /dev/null and b/docs/src/assets/blog-images/remote-linux.webp differ diff --git a/docs/src/assets/blog-images/remote-mac.webp b/docs/src/assets/blog-images/remote-mac.webp new file mode 100644 index 000000000..bf4758e1a Binary files /dev/null and b/docs/src/assets/blog-images/remote-mac.webp differ diff --git a/docs/src/assets/blog-images/remote.webp b/docs/src/assets/blog-images/remote.webp new file mode 100644 index 000000000..3968c5ddc Binary files /dev/null and b/docs/src/assets/blog-images/remote.webp differ diff --git a/docs/src/assets/blog-images/vscode.webp b/docs/src/assets/blog-images/vscode.webp new file mode 100644 index 000000000..156fa3078 Binary files /dev/null and b/docs/src/assets/blog-images/vscode.webp differ diff --git a/docs/src/assets/blog-images/wails-linux.webp b/docs/src/assets/blog-images/wails-linux.webp new file mode 100644 index 000000000..45c76ed0e Binary files /dev/null and b/docs/src/assets/blog-images/wails-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-mac.webp b/docs/src/assets/blog-images/wails-mac.webp new file mode 100644 index 000000000..26ee0be13 Binary files /dev/null and b/docs/src/assets/blog-images/wails-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-linux.webp b/docs/src/assets/blog-images/wails-menus-linux.webp new file mode 100644 index 000000000..391225a35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-mac.webp b/docs/src/assets/blog-images/wails-menus-mac.webp new file mode 100644 index 000000000..033f68d35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus.webp b/docs/src/assets/blog-images/wails-menus.webp new file mode 100644 index 000000000..ba64152b4 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus.webp differ diff --git a/docs/src/assets/blog-images/wails.webp b/docs/src/assets/blog-images/wails.webp new file mode 100644 index 000000000..777f4d05c Binary files /dev/null and b/docs/src/assets/blog-images/wails.webp differ diff --git a/docs/src/assets/contributors.html b/docs/src/assets/contributors.html new file mode 100644 index 000000000..465a64801 --- /dev/null +++ b/docs/src/assets/contributors.html @@ -0,0 +1,246 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lea Anthony
Lea Anthony

๐Ÿ’ป ๐Ÿค” ๐ŸŽจ ๐Ÿ–‹ ๐Ÿ’ก ๐Ÿง‘โ€๐Ÿซ ๐Ÿ“† ๐Ÿ”ง ๐Ÿ› ๐Ÿ“ ๐Ÿšง ๐Ÿ“ฆ ๐Ÿ‘€ ๐Ÿ’ฌ ๐Ÿ”ฌ โš ๏ธ โœ… ๐Ÿ“ข ๐Ÿ‘€ ๐Ÿ“–
stffabi
stffabi

๐Ÿ’ป ๐Ÿค” ๐ŸŽจ ๐Ÿ› ๐Ÿšง ๐Ÿ“ฆ ๐Ÿ‘€ ๐Ÿ’ฌ ๐Ÿ”ฌ ๐Ÿ‘€ ๐Ÿ“– โš ๏ธ
Travis McLane
Travis McLane

๐Ÿ’ป ๐Ÿ”ฌ ๐Ÿ“ฆ ๐Ÿค” ๐Ÿ› ๐Ÿ‘€ โš ๏ธ ๐Ÿ’ฌ ๐Ÿ“–
Misite Bao
Misite Bao

๐Ÿ“– ๐ŸŒ ๐Ÿ”ฌ ๐Ÿšง
Byron Chris
Byron Chris

๐Ÿ’ป ๐Ÿ”ฌ ๐Ÿšง ๐Ÿ› ๐Ÿ‘€ โš ๏ธ ๐Ÿ’ฌ ๐Ÿค” ๐ŸŽจ ๐Ÿ“ฆ ๐Ÿš‡
konez2k
konez2k

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿค”
Dario Emerson
Dario Emerson

๐Ÿ’ป ๐Ÿ› ๐Ÿค” โš ๏ธ
Ian M. Jones
Ian M. Jones

๐Ÿ’ป ๐Ÿ› ๐Ÿค” โš ๏ธ ๐Ÿ‘€ ๐Ÿ“ฆ
marktohark
marktohark

๐Ÿ’ป
Ryan H
Ryan H

๐Ÿ’ป
Cody Bentley
Cody Bentley

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿค” ๐Ÿ’ต
Florent
Florent

๐Ÿ’ป ๐Ÿ›
Alexander Hudek
Alexander Hudek

๐Ÿ’ป ๐Ÿ’ต
Tim Kipp
Tim Kipp

๐Ÿ’ป
Altynbek Kaliakbarov
Altynbek Kaliakbarov

๐Ÿ’ป
Nikolai Zimmermann
Nikolai Zimmermann

๐Ÿ’ป
k-muchmore
k-muchmore

๐Ÿ’ป
Snider
Snider

๐Ÿ’ป ๐Ÿค” ๐Ÿ“– ๐Ÿ’ต
Albert Sun
Albert Sun

๐Ÿ’ป โš ๏ธ
Ariel
Ariel

๐Ÿ’ป ๐Ÿ›
Ilgฤฑt Yฤฑldฤฑrฤฑm
Ilgฤฑt Yฤฑldฤฑrฤฑm

๐Ÿ’ป ๐Ÿ› ๐Ÿ’ต
Toyam Cox
Toyam Cox

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿ›
hi019
hi019

๐Ÿ’ป ๐Ÿ›
Arthur Wiebe
Arthur Wiebe

๐Ÿ’ป ๐Ÿ›
Balakrishna Prasad Ganne
Balakrishna Prasad Ganne

๐Ÿ’ป
BillBuilt
BillBuilt

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿค” ๐Ÿ’ฌ ๐Ÿ’ต
Eng Zer Jun
Eng Zer Jun

๐Ÿšง ๐Ÿ’ป
LGiki
LGiki

๐Ÿ“–
Lontten
Lontten

๐Ÿ“–
Lukas Crepaz
Lukas Crepaz

๐Ÿ’ป ๐Ÿ›
Marcus Crane
Marcus Crane

๐Ÿ› ๐Ÿ“– ๐Ÿ’ต
Qais Patankar
Qais Patankar

๐Ÿ“–
Wakeful-Cloud
Wakeful-Cloud

๐Ÿ’ป ๐Ÿ›
Zรกmbรณ, Levente
Zรกmbรณ, Levente

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿ› โš ๏ธ
Ironpark
Ironpark

๐Ÿ’ป ๐Ÿค”
mondy
mondy

๐Ÿ’ป ๐Ÿ“–
Benjamin Ryan
Benjamin Ryan

๐Ÿ›
fallendusk
fallendusk

๐Ÿ“ฆ ๐Ÿ’ป
Mat Ryer
Mat Ryer

๐Ÿ’ป ๐Ÿค” ๐Ÿ›
Abtin
Abtin

๐Ÿ’ป ๐Ÿ›
Adrian Lanzafame
Adrian Lanzafame

๐Ÿ“ฆ ๐Ÿ’ป
Aleksey Polyakov
Aleksey Polyakov

๐Ÿ› ๐Ÿ’ป
Alexander Matviychuk
Alexander Matviychuk

๐Ÿ’ป ๐Ÿ“ฆ
AlienRecall
AlienRecall

๐Ÿ’ป ๐Ÿ“ฆ
Aman
Aman

๐Ÿ“–
Amaury Tobias Quiroz
Amaury Tobias Quiroz

๐Ÿ’ป ๐Ÿ›
Andreas Wenk
Andreas Wenk

๐Ÿ“–
Antonio Stankoviฤ‡
Antonio Stankoviฤ‡

๐Ÿ’ป ๐Ÿ“ฆ
Arpit Jain
Arpit Jain

๐Ÿ“–
Austin Schey
Austin Schey

๐Ÿ’ป ๐Ÿ›
Benjamin Thomas
Benjamin Thomas

๐Ÿ’ป ๐Ÿ“ฆ ๐Ÿค”
Bertram Truong
Bertram Truong

๐Ÿ’ป ๐Ÿ›
Blake Bourque
Blake Bourque

๐Ÿ“–
Denis
Denis

๐Ÿ“–
diogox
diogox

๐Ÿ’ป ๐Ÿ“ฆ
Dmitry Gomzyakov
Dmitry Gomzyakov

๐Ÿ’ป ๐Ÿ“ฆ
Edward Browncross
Edward Browncross

๐Ÿ’ป
Elie Grenon
Elie Grenon

๐Ÿ’ป
Florian Didron
Florian Didron

๐Ÿ’ป ๐Ÿ› ๐Ÿค” โš ๏ธ ๐Ÿ‘€ ๐Ÿ“ฆ
GargantuaX
GargantuaX

๐Ÿ’ต
Igor Minin
Igor Minin

๐Ÿ’ป ๐Ÿ›
Jae-Sung Lee
Jae-Sung Lee

๐Ÿ’ป ๐Ÿค”
Jarek
Jarek

๐Ÿ’ป ๐Ÿ“ฆ
Junker
Junker

๐Ÿ“–
Kris Raney
Kris Raney

๐Ÿ’ป ๐Ÿ›
Luken
Luken

๐Ÿ“–
Mark Stenglein
Mark Stenglein

๐Ÿ’ป ๐Ÿ›
buddyabaddon
buddyabaddon

๐Ÿ’ป
MikeSchaap
MikeSchaap

๐Ÿ’ป ๐Ÿ›
NYSSEN Michaรซl
NYSSEN Michaรซl

๐Ÿ’ป ๐Ÿ›
Nan0
Nan0

๐Ÿ’ป ๐Ÿค” โš ๏ธ ๐Ÿ‘€
oskar
oskar

๐Ÿ“–
Pierre Joye
Pierre Joye

๐Ÿ’ป ๐Ÿ› ๐Ÿค” โš ๏ธ
Reuben Thomas-Davis
Reuben Thomas-Davis

๐Ÿ’ป ๐Ÿ›
Robin
Robin

๐Ÿ’ป ๐Ÿ›
Sebastian Bauer
Sebastian Bauer

๐Ÿ’ป ๐Ÿค” โš ๏ธ ๐Ÿ‘€ ๐Ÿ’ฌ
Sidharth Rathi
Sidharth Rathi

๐Ÿ“– ๐Ÿ›
Sithembiso Khumalo
Sithembiso Khumalo

๐Ÿ’ป ๐Ÿ›
Soheib El-Harrache
Soheib El-Harrache

๐Ÿ’ป ๐Ÿ› ๐Ÿ’ต
Sophie Au
Sophie Au

๐Ÿ’ป ๐Ÿ›
Stefanos Papadakis
Stefanos Papadakis

๐Ÿ’ป ๐Ÿ›
Steve Chung
Steve Chung

๐Ÿ’ป ๐Ÿ›
Timm Ortloff
Timm Ortloff

๐Ÿ“–
Tom
Tom

๐Ÿ’ป
Valentin Trinquรฉ
Valentin Trinquรฉ

๐Ÿ’ป ๐Ÿ›
mattn
mattn

๐Ÿ’ป ๐Ÿ›
bearsh
bearsh

๐Ÿ’ป ๐Ÿค” ๐Ÿ“–
chenxiao
chenxiao

๐Ÿ’ป ๐Ÿค” ๐Ÿ“–
fengweiqiang
fengweiqiang

๐Ÿ’ป ๐Ÿ“ฆ
flin7
flin7

๐Ÿ“–
fred21O4
fred21O4

๐Ÿ“–
gardc
gardc

๐Ÿ“– โœ…
rayshoo
rayshoo

๐Ÿ“–
Ishiyama Yuzuki
Ishiyama Yuzuki

๐Ÿ’ป ๐Ÿ›
ไฝฐ้˜…
ไฝฐ้˜…

๐Ÿ’ป
ๅˆ€ๅˆ€
ๅˆ€ๅˆ€

๐Ÿ“– ๐Ÿ›
ๅฝ’ไฝ
ๅฝ’ไฝ

๐Ÿ’ป ๐Ÿ›
skamensky
skamensky

๐Ÿ’ป ๐Ÿค” ๐Ÿ“–
dependabot[bot]
dependabot[bot]

๐Ÿ’ป ๐Ÿšง
Damian Sieradzki
Damian Sieradzki

๐Ÿ’ต
John Dorman
John Dorman

๐Ÿ’ต
Ian Sinnott
Ian Sinnott

๐Ÿ’ต
Arden Shackelford
Arden Shackelford

๐Ÿ’ต
Bironou
Bironou

๐Ÿ’ต
CharlieGo_
CharlieGo_

๐Ÿ’ต
overnet
overnet

๐Ÿ’ต
jugglingjsons
jugglingjsons

๐Ÿ’ต
Selvin Ortiz
Selvin Ortiz

๐Ÿ’ต
ZanderCodes
ZanderCodes

๐Ÿ’ต
Michael Voronov
Michael Voronov

๐Ÿ’ต
letheanVPN
letheanVPN

๐Ÿ’ต
Tai Groot
Tai Groot

๐Ÿ’ต
easy-web-it
easy-web-it

๐Ÿ’ต
Michael Olson
Michael Olson

๐Ÿ’ต
EdenNetwork Italia
EdenNetwork Italia

๐Ÿ’ต
ondoki
ondoki

๐Ÿ’ต
QuEST Rail LLC
QuEST Rail LLC

๐Ÿ’ต
Gilgameลก
Gilgameลก

๐Ÿ’ต
Bernt-Johan Bergshaven
Bernt-Johan Bergshaven

๐Ÿ’ต
Liam Bigelow
Liam Bigelow

๐Ÿ’ต
Nick Arellano
Nick Arellano

๐Ÿ’ต
Frank Chiarulli Jr.
Frank Chiarulli Jr.

๐Ÿ’ต
Tyler
Tyler

๐Ÿ’ต
Trea Hauet
Trea Hauet

๐Ÿ’ต
Kent 'picat' Gruber
Kent 'picat' Gruber

๐Ÿ’ต
tc-hib
tc-hib

๐Ÿ’ต
Antonio
Antonio

๐Ÿ“–
MyNameIsAres
MyNameIsAres

๐Ÿ“–
Maicarons J
Maicarons J

๐Ÿ“–
kiddov
kiddov

๐Ÿ“– ๐Ÿ’ต โš ๏ธ ๐Ÿค”
Nicolas Coutin
Nicolas Coutin

๐Ÿ’ต
Parvin Eyvazov
Parvin Eyvazov

๐Ÿ“–
github-actions[bot]
github-actions[bot]

๐Ÿ’ป
Oleg Gulevskyy
Oleg Gulevskyy

๐Ÿ’ป ๐Ÿ“– ๐Ÿšง ๐Ÿ“ฆ
Richard Guay
Richard Guay

๐Ÿ“–
Adam Tenderholt
Adam Tenderholt

๐Ÿ’ป
JulioDRF
JulioDRF

๐Ÿ’ป
Scott Opell
Scott Opell

๐Ÿ’ป
Vadim Shchepotev
Vadim Shchepotev

๐Ÿ’ป
Will Andrews
Will Andrews

๐Ÿ’ป
Gwyn
Gwyn

๐Ÿ’ป ๐Ÿ‘€ ๐Ÿ’ฌ ๐Ÿ”ฌ
ๅธŒๅ˜‰ๅ˜‰
ๅธŒๅ˜‰ๅ˜‰

๐Ÿ’ป
ALMAS
ALMAS

๐Ÿ’ป
Alex
Alex

๐Ÿ’ป
Arif Ali
Arif Ali

๐Ÿ’ป
Artur Siarohau
Artur Siarohau

๐Ÿ’ป
Binyamin Aron Green
Binyamin Aron Green

๐Ÿ’ป
Brian Dwyer
Brian Dwyer

๐Ÿ’ป
Christian Kilb
Christian Kilb

๐Ÿ’ป
David Florness
David Florness

๐Ÿ“–
David Walton
David Walton

๐Ÿ’ป
Debdut Karmakar
Debdut Karmakar

๐Ÿ’ป
Dieter Zhu
Dieter Zhu

๐Ÿ’ป
Fredrik Holmqvist
Fredrik Holmqvist

๐Ÿ’ป
Giovanni Palma
Giovanni Palma

๐Ÿ’ป
Hao
Hao

๐Ÿ’ป
Igor Sementsov
Igor Sementsov

๐Ÿ’ป
Johannes Haseitl
Johannes Haseitl

๐Ÿ’ป
Joshua Hull
Joshua Hull

๐Ÿ’ป
Joshua Mangiola
Joshua Mangiola

๐Ÿ“–
Kevin MacMartin
Kevin MacMartin

๐Ÿ’ป
Liang Li
Liang Li

๐Ÿ’ป
Marvin Collins Hosea
Marvin Collins Hosea

๐Ÿ’ป
Matt Holt
Matt Holt

๐Ÿ’ป
Niklas
Niklas

๐Ÿ’ป
Andy Hsu
Andy Hsu

๐Ÿ’ป
NullCode
NullCode

๐Ÿ’ป
Oussama Sethoum
Oussama Sethoum

๐Ÿ’ป
ParkourLiu
ParkourLiu

๐Ÿ’ป
Rachel Chen
Rachel Chen

๐Ÿ’ป
Rob Nice
Rob Nice

๐Ÿ’ป
Ryo TAGAMI
Ryo TAGAMI

๐Ÿ’ป
Sam Hennessy
Sam Hennessy

๐Ÿ’ป
Sean
Sean

๐Ÿ’ป
Sean Gosiaco
Sean Gosiaco

๐Ÿ’ป
Eric P Sheets
Eric P Sheets

๐Ÿ’ป
Supian M
Supian M

๐Ÿ’ป
Watson-Sei
Watson-Sei

๐Ÿ’ป ๐Ÿ“–
Yuki Shindo
Yuki Shindo

๐Ÿ’ป
cuigege
cuigege

๐Ÿ’ป
cybertramp
cybertramp

๐Ÿ’ป
hiroki yagi
hiroki yagi

๐Ÿ’ป
imgbot[bot]
imgbot[bot]

๐Ÿ’ป
juju
juju

๐Ÿ’ป
Michael Eatherly
Michael Eatherly

๐Ÿ’ป
tk
tk

๐Ÿ’ป
allcontributors[bot]
allcontributors[bot]

๐Ÿ“–
wander
wander

๐Ÿ“–
+ + +
\ No newline at end of file diff --git a/docs/src/assets/menus/context-menu.png b/docs/src/assets/menus/context-menu.png new file mode 100644 index 000000000..dd43c0c7f --- /dev/null +++ b/docs/src/assets/menus/context-menu.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTIzODU1NjI4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTIzODU1NjM4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMjM4NTU2MDhGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMjM4NTU2MThGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAAALAAAAAAsASwBAAL/hI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpfMpvMJjUqn1Kr1is1qt9yu9wsOi8fksvmMTqvX7Lb7DY/L5/S6/Y7P6/f8vv8PGCg4SFhoeIiYqLjI2Oj4CBkpOUlZaXmJmam5ydnp+QkaKjpKWmp6ipqqusra6voKGys7S1tre4ubq7vL2+v7CxwsPExcbHyMnKy8zNzs/AwdLT1NXW19jZ2tvc3d7f0NHi4+Tl5ufo6err7O3u7+Dh8vP09fb3+Pn6+/z9/v/w8woMCBBAsaPIgwocKFDBs6fAgxosSJFCtavIgxo8aN/xw7evwIMqTIkSRLmjyJMqXKlSxbunwJM6bMmTRr2ryJM6fOnTx7+vwJNKjQoUSLGj2KNKnSpUybOn0KNarUqVSrWr2KNavWrVy7ev0KNqzYsWTLmj2LNq3atWzbun0LN67cuXTr2r2LN6/evXz7+v0LOLDgwYQLGz6MOLHixYwbO34MObLkyZQrW76MObPmzZw7e/4MOrTo0aRLmz6NOrXq1axbu34NO7bs2bRr276NO7fu3bx7+/4NPLjw4cSLGz+OPLny5cybO38OPbr06dSrW7+OPbv27dy7e/8OPrz48eTLmz+PPr369ezbu38PP778+fTr27+PP7/+/fz7+9OPX7//fwAGKOCABBZo4IEIJqjgggw26OCDEEYo4YQUVmjhhRhmqOGGHHbo4YcghijiiCSWaOKJKKao4oostujiizDGKOOMNNZo44045qjjjjz26OOPQAYp5JBEFmnkkUgmqeSSTDbp5JNQRinllFRWaeWVWGap5ZZcdunll2CGKeaYZJZp5plopqnmmmy26eabcMYp55x01mnnnXjmqeeefPbp55+ABirooIQWauihiCaq6KKMNuroo5BGKumklFZq6aWYZqrpppx26umnoIYq6qiklmrqqaimquqqrLbq6quwxirrqxEAADs= diff --git a/docs/src/assets/qr1.png b/docs/src/assets/qr1.png new file mode 100644 index 000000000..69885acbe Binary files /dev/null and b/docs/src/assets/qr1.png differ diff --git a/docs/src/assets/qr2.png b/docs/src/assets/qr2.png new file mode 100644 index 000000000..e2ce2a5f1 Binary files /dev/null and b/docs/src/assets/qr2.png differ diff --git a/docs/src/assets/showcase-images/bboard.webp b/docs/src/assets/showcase-images/bboard.webp new file mode 100644 index 000000000..463d25de0 Binary files /dev/null and b/docs/src/assets/showcase-images/bboard.webp differ diff --git a/docs/src/assets/showcase-images/cfntracker.webp b/docs/src/assets/showcase-images/cfntracker.webp new file mode 100644 index 000000000..6a2288a5c Binary files /dev/null and b/docs/src/assets/showcase-images/cfntracker.webp differ diff --git a/docs/src/assets/showcase-images/clave.png b/docs/src/assets/showcase-images/clave.png new file mode 100644 index 000000000..3ee29c303 Binary files /dev/null and b/docs/src/assets/showcase-images/clave.png differ diff --git a/docs/src/assets/showcase-images/emailit.webp b/docs/src/assets/showcase-images/emailit.webp new file mode 100644 index 000000000..fc1b9a51a Binary files /dev/null and b/docs/src/assets/showcase-images/emailit.webp differ diff --git a/docs/src/assets/showcase-images/encrypteasy.webp b/docs/src/assets/showcase-images/encrypteasy.webp new file mode 100644 index 000000000..c0789a3e3 Binary files /dev/null and b/docs/src/assets/showcase-images/encrypteasy.webp differ diff --git a/docs/src/assets/showcase-images/esp-studio.png b/docs/src/assets/showcase-images/esp-studio.png new file mode 100644 index 000000000..9cb7d39fa Binary files /dev/null and b/docs/src/assets/showcase-images/esp-studio.png differ diff --git a/docs/src/assets/showcase-images/filehound.webp b/docs/src/assets/showcase-images/filehound.webp new file mode 100644 index 000000000..92769ca8e Binary files /dev/null and b/docs/src/assets/showcase-images/filehound.webp differ diff --git a/docs/src/assets/showcase-images/hiposter.webp b/docs/src/assets/showcase-images/hiposter.webp new file mode 100644 index 000000000..7c7510ea1 Binary files /dev/null and b/docs/src/assets/showcase-images/hiposter.webp differ diff --git a/docs/src/assets/showcase-images/mac-app.png b/docs/src/assets/showcase-images/mac-app.png new file mode 100644 index 000000000..1f4c85304 Binary files /dev/null and b/docs/src/assets/showcase-images/mac-app.png differ diff --git a/docs/src/assets/showcase-images/mchat.png b/docs/src/assets/showcase-images/mchat.png new file mode 100644 index 000000000..0942c482b Binary files /dev/null and b/docs/src/assets/showcase-images/mchat.png differ diff --git a/docs/src/assets/showcase-images/minecraft-mod-updater.webp b/docs/src/assets/showcase-images/minecraft-mod-updater.webp new file mode 100644 index 000000000..c8e011cf3 Binary files /dev/null and b/docs/src/assets/showcase-images/minecraft-mod-updater.webp differ diff --git a/docs/src/assets/showcase-images/minesweeper-xp.webp b/docs/src/assets/showcase-images/minesweeper-xp.webp new file mode 100644 index 000000000..b3c5ca26b Binary files /dev/null and b/docs/src/assets/showcase-images/minesweeper-xp.webp differ diff --git a/docs/src/assets/showcase-images/modalfilemanager.webp b/docs/src/assets/showcase-images/modalfilemanager.webp new file mode 100644 index 000000000..2fdf219fc Binary files /dev/null and b/docs/src/assets/showcase-images/modalfilemanager.webp differ diff --git a/docs/src/assets/showcase-images/mollywallet.webp b/docs/src/assets/showcase-images/mollywallet.webp new file mode 100644 index 000000000..11641f8ca Binary files /dev/null and b/docs/src/assets/showcase-images/mollywallet.webp differ diff --git a/docs/src/assets/showcase-images/october.webp b/docs/src/assets/showcase-images/october.webp new file mode 100644 index 000000000..ceec1c573 Binary files /dev/null and b/docs/src/assets/showcase-images/october.webp differ diff --git a/docs/src/assets/showcase-images/optimus.webp b/docs/src/assets/showcase-images/optimus.webp new file mode 100644 index 000000000..0aac84058 Binary files /dev/null and b/docs/src/assets/showcase-images/optimus.webp differ diff --git a/docs/src/assets/showcase-images/portfall.webp b/docs/src/assets/showcase-images/portfall.webp new file mode 100644 index 000000000..12f8d6e5b Binary files /dev/null and b/docs/src/assets/showcase-images/portfall.webp differ diff --git a/docs/src/assets/showcase-images/resizem.webp b/docs/src/assets/showcase-images/resizem.webp new file mode 100644 index 000000000..aaee1c806 Binary files /dev/null and b/docs/src/assets/showcase-images/resizem.webp differ diff --git a/docs/src/assets/showcase-images/restic-browser-2.png b/docs/src/assets/showcase-images/restic-browser-2.png new file mode 100644 index 000000000..b986bfe68 Binary files /dev/null and b/docs/src/assets/showcase-images/restic-browser-2.png differ diff --git a/docs/src/assets/showcase-images/riftshare-main.webp b/docs/src/assets/showcase-images/riftshare-main.webp new file mode 100644 index 000000000..2d6a8fb3a Binary files /dev/null and b/docs/src/assets/showcase-images/riftshare-main.webp differ diff --git a/docs/src/assets/showcase-images/scriptbar.webp b/docs/src/assets/showcase-images/scriptbar.webp new file mode 100644 index 000000000..92463fdb9 Binary files /dev/null and b/docs/src/assets/showcase-images/scriptbar.webp differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png new file mode 100644 index 000000000..0f34e4f53 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png new file mode 100644 index 000000000..d87878c29 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png new file mode 100644 index 000000000..b82468c80 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png differ diff --git a/docs/src/assets/showcase-images/surge.png b/docs/src/assets/showcase-images/surge.png new file mode 100644 index 000000000..d732fabe8 Binary files /dev/null and b/docs/src/assets/showcase-images/surge.png differ diff --git a/docs/src/assets/showcase-images/tiny-rdm1.webp b/docs/src/assets/showcase-images/tiny-rdm1.webp new file mode 100644 index 000000000..11b375580 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm1.webp differ diff --git a/docs/src/assets/showcase-images/tiny-rdm2.webp b/docs/src/assets/showcase-images/tiny-rdm2.webp new file mode 100644 index 000000000..9de730fb4 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm2.webp differ diff --git a/docs/src/assets/showcase-images/varly2.webp b/docs/src/assets/showcase-images/varly2.webp new file mode 100644 index 000000000..6dbe0c9bf Binary files /dev/null and b/docs/src/assets/showcase-images/varly2.webp differ diff --git a/docs/src/assets/showcase-images/wailsterm.webp b/docs/src/assets/showcase-images/wailsterm.webp new file mode 100644 index 000000000..6d4251a75 Binary files /dev/null and b/docs/src/assets/showcase-images/wailsterm.webp differ diff --git a/docs/src/assets/showcase-images/wally.webp b/docs/src/assets/showcase-images/wally.webp new file mode 100644 index 000000000..150c98c74 Binary files /dev/null and b/docs/src/assets/showcase-images/wally.webp differ diff --git a/docs/src/assets/showcase-images/warmine1.png b/docs/src/assets/showcase-images/warmine1.png new file mode 100644 index 000000000..38441d99d Binary files /dev/null and b/docs/src/assets/showcase-images/warmine1.png differ diff --git a/docs/src/assets/showcase-images/warmine2.png b/docs/src/assets/showcase-images/warmine2.png new file mode 100644 index 000000000..4713462da Binary files /dev/null and b/docs/src/assets/showcase-images/warmine2.png differ diff --git a/docs/src/assets/showcase-images/wombat.webp b/docs/src/assets/showcase-images/wombat.webp new file mode 100644 index 000000000..97f965834 Binary files /dev/null and b/docs/src/assets/showcase-images/wombat.webp differ diff --git a/docs/src/assets/showcase-images/ytd.webp b/docs/src/assets/showcase-images/ytd.webp new file mode 100644 index 000000000..c7988cee5 Binary files /dev/null and b/docs/src/assets/showcase-images/ytd.webp differ diff --git a/docs/src/assets/wails-logo-dark.svg b/docs/src/assets/wails-logo-dark.svg new file mode 100644 index 000000000..dfc54485c --- /dev/null +++ b/docs/src/assets/wails-logo-dark.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails-logo-horizontal-dark.svg b/docs/src/assets/wails-logo-horizontal-dark.svg new file mode 100644 index 000000000..01808dca5 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-horizontal-light.svg b/docs/src/assets/wails-logo-horizontal-light.svg new file mode 100644 index 000000000..465bc5cf9 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-light.svg b/docs/src/assets/wails-logo-light.svg new file mode 100644 index 000000000..ab02c827a --- /dev/null +++ b/docs/src/assets/wails-logo-light.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails_build.mp4 b/docs/src/assets/wails_build.mp4 new file mode 100644 index 000000000..2d1ee783b Binary files /dev/null and b/docs/src/assets/wails_build.mp4 differ diff --git a/docs/src/assets/wails_dev.mp4 b/docs/src/assets/wails_dev.mp4 new file mode 100644 index 000000000..29c2a45a4 Binary files /dev/null and b/docs/src/assets/wails_dev.mp4 differ diff --git a/docs/src/assets/wails_init.mp4 b/docs/src/assets/wails_init.mp4 new file mode 100644 index 000000000..337bc0f5a Binary files /dev/null and b/docs/src/assets/wails_init.mp4 differ diff --git a/docs/src/components/CardAnimation.astro b/docs/src/components/CardAnimation.astro new file mode 100644 index 000000000..5d3de111b --- /dev/null +++ b/docs/src/components/CardAnimation.astro @@ -0,0 +1,62 @@ +--- +--- + diff --git a/docs/src/components/Mermaid.astro b/docs/src/components/Mermaid.astro new file mode 100644 index 000000000..65b7e3ca9 --- /dev/null +++ b/docs/src/components/Mermaid.astro @@ -0,0 +1,59 @@ +--- +export interface Props { + title?: string; +} + +const { title = "" } = Astro.props; +--- + + + +
+
{title}
+
Loading diagram...
+
+ Source +
+
+
diff --git a/docs/src/content/authors.ts b/docs/src/content/authors.ts new file mode 100644 index 000000000..0500490f3 --- /dev/null +++ b/docs/src/content/authors.ts @@ -0,0 +1,11 @@ +import type { StarlightBlogUserConfig } from "starlight-blog"; + +type Authors = NonNullable["authors"]; +export const authors: Authors = { + leaanthony: { + name: "Lea Anthony", + title: "Maintainer of Wails", + url: "https://github.com/leaanthony", + picture: "https://github.com/leaanthony.png", + }, +}; diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts new file mode 100644 index 000000000..dae3197fe --- /dev/null +++ b/docs/src/content/config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from "astro:content"; +import { docsSchema, i18nSchema } from "@astrojs/starlight/schema"; +import { blogSchema } from "starlight-blog/schema"; + +export const collections = { + i18n: defineCollection({ type: "data", schema: i18nSchema() }), + docs: defineCollection({ + schema: docsSchema({ extend: (context) => blogSchema(context) }), + }), +}; diff --git a/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md new file mode 100644 index 000000000..dbc8d9776 --- /dev/null +++ b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md @@ -0,0 +1,206 @@ +--- +slug: blog/wails-v2-beta-for-windows +title: Wails v2 Beta for Windows +authors: [leaanthony] +tags: [wails, v2] +date: 2021-09-27 +--- + +![wails screenshot](../../../assets/blog-images/wails.webp) + +When I first announced Wails on Reddit, just over 2 years ago from a train in +Sydney, I did not expect it to get much attention. A few days later, a prolific +tech vlogger released a tutorial video, gave it a positive review and from that +point on, interest in the project has skyrocketed. + +It was clear that people were excited about adding web frontends to their Go +projects, and almost immediately pushed the project beyond the proof of concept +that I had created. At the time, Wails used the +[webview](https://github.com/webview/webview) project to handle the frontend, +and the only option for Windows was the IE11 renderer. Many bug reports were +rooted in this limitation: poor JavaScript/CSS support and no dev tools to debug +it. This was a frustrating development experience but there wasn't much that +could have been done to rectify it. + +For a long time, I'd firmly believed that Microsoft would eventually have to +sort out their browser situation. The world was moving on, frontend development +was booming and IE wasn't cutting it. When Microsoft announced the move to using +Chromium as the basis for their new browser direction, I knew it was only a +matter of time until Wails could use it, and move the Windows developer +experience to the next level. + +Today, I am pleased to announce: **Wails v2 Beta for Windows**! There's a huge +amount to unpack in this release, so grab a drink, take a seat and we'll +begin... + +### No CGO Dependency! + +No, I'm not joking: _No_ _CGO_ _dependency_ ๐Ÿคฏ! The thing about Windows is that, +unlike MacOS and Linux, it doesn't come with a default compiler. In addition, +CGO requires a mingw compiler and there's a ton of different installation +options. Removing the CGO requirement has massively simplified setup, as well as +making debugging an awful lot easier. Whilst I have put a fair bit of effort in +getting this working, the majority of the credit should go to +[John Chadwick](https://github.com/jchv) for not only starting a couple of +projects to make this possible, but also being open to someone taking those +projects and building on them. Credit also to +[Tad Vizbaras](https://github.com/tadvi) whose +[winc](https://github.com/tadvi/winc) project started me down this path. + +### WebView2 Chromium Renderer + +![devtools screenshot](../../../assets/blog-images/devtools.png) + +Finally, Windows developers get a first class rendering engine for their +applications! Gone are the days of contorting your frontend code to work on +Windows. On top of that, you get a first-class developer tools experience! + +The WebView2 component does, however, have a requirement to have the +`WebView2Loader.dll` sitting alongside the binary. This makes distribution just +that little bit more painful than we gophers are used to. All solutions and +libraries (that I know of) that use WebView2 have this dependency. + +However, I'm really excited to announce that Wails applications _have no such +requirement_! Thanks to the wizardry of +[John Chadwick](https://github.com/jchv), we are able to bundle this dll inside +the binary and get Windows to load it as if it were present on disk. + +Gophers rejoice! The single binary dream lives on! + +### New Features + +![wails-menus screenshot](../../../assets/blog-images/wails-menus.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +There is now the option to generate IDE configuration along with your project. +This means that if you open your project in a supported IDE, it will already be +configured for building and debugging your application. Currently VSCode is +supported but we hope to support other IDEs such as Goland soon. + +![vscode screenshot](../../../assets/blog-images/vscode.webp) + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +![browser screenshot](../../../assets/blog-images/browser.webp) + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote screenshot](../../../assets/blog-images/remote.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### In Conclusion + +Wails v2 represents a new foundation for the project. The aim of this release is +to get feedback on the new approach, and to iron out any bugs before a full +release. Your input would be most welcome. Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +There were many twists and turns, pivots and u-turns to get to this point. This +was due partly to early technical decisions that needed changing, and partly +because some core problems we had spent time building workarounds for were fixed +upstream: Goโ€™s embed feature is a good example. Fortunately, everything came +together at the right time, and today we have the very best solution that we can +have. I believe the wait has been worth it - this would not have been possible +even 2 months ago. + +I also need to give a huge thank you :pray: to the following people because +without them, this release just wouldn't exist: + +- [Misite Bao](https://github.com/misitebao) - An absolute workhorse on the + Chinese translations and an incredible bug finder. +- [John Chadwick](https://github.com/jchv) - His amazing work on + [go-webview2](https://github.com/jchv/go-webview2) and + [go-winloader](https://github.com/jchv/go-winloader) have made the Windows + version we have today possible. +- [Tad Vizbaras](https://github.com/tadvi) - Experimenting with his + [winc](https://github.com/tadvi/winc) project was the first step down the path + to a pure Go Wails. +- [Mat Ryer](https://github.com/matryer) - His support, encouragement and + feedback has really helped drive the project forward. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drives the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: MacOS and Linux users need not feel left out - porting to this new +foundation is actively under way and most of the hard work has already been +done. Hang in there! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md new file mode 100644 index 000000000..31d53e1bc --- /dev/null +++ b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md @@ -0,0 +1,166 @@ +--- +slug: blog/wails-v2-beta-for-mac +title: Wails v2 Beta for MacOS +authors: [leaanthony] +tags: [wails, v2] +date: 2021-11-08 +--- + +![wails-mac screenshot](../../../assets/blog-images/wails-mac.webp) + +Today marks the first beta release of Wails v2 for Mac! It's taken quite a while +to get to this point and I'm hoping that today's release will give you something +that's reasonably useful. There have been a number of twists and turns to get to +this point and I'm hoping, with your help, to iron out the crinkles and get the +Mac port polished for the final v2 release. + +You mean this isn't ready for production? For your use case, it may well be +ready, but there are still a number of known issues so keep your eye on +[this project board](https://github.com/wailsapp/wails/projects/7) and if you +would like to contribute, you'd be very welcome! + +So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the +Windows Beta :wink: + +### New Features + +![wails-menus-mac screenshot](../../../assets/blog-images/wails-menus-mac.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### Mac Specific Options + +In addition to the normal application options, Wails v2 for Mac also brings some +Mac extras: + +- Make your window all funky and translucent, like all the pretty swift apps! +- Highly customisable titlebar +- We support the NSAppearance options for the application +- Simple config to auto-create an "About" menu + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-mac screenshot](../../../assets/blog-images/remote-mac.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Native M1 Support + +Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the +Wails project now supports M1 native builds: + +![build-darwin-arm screenshot](../../../assets/blog-images/build-darwin-arm.webp) + +You can also specify `darwin/amd64` as a target too: + +![build-darwin-amd screenshot](../../../assets/blog-images/build-darwin-amd.webp) + +Oh, I almost forgot.... you can also do `darwin/universal`.... :wink: + +![build-darwin-universal screenshot](../../../assets/blog-images/build-darwin-universal.webp) + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/build-cross-windows.webp) +bu + +### WKWebView Renderer + +V1 relied on a (now deprecated) WebView component. V2 uses the most recent +WKWebKit component so expect the latest and greatest from Apple. + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drive the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: Linux users, you're next! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md new file mode 100644 index 000000000..047236258 --- /dev/null +++ b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md @@ -0,0 +1,129 @@ +--- +slug: blog/wails-v2-beta-for-linux +title: Wails v2 Beta for Linux +authors: [leaanthony] +tags: [wails, v2] +date: 2022-02-22 +--- + +![wails-linux screenshot](../../../assets/blog-images/wails-linux.webp) + +I'm pleased to finally announce that Wails v2 is now in beta for Linux! It is +somewhat ironic that the very first experiments with v2 was on Linux and yet it +has ended up as the last release. That being said, the v2 we have today is very +different from those first experiments. So without further ado, let's go over +the new features: + +### New Features + +![wails-menus-linux screenshot](../../../assets/blog-images/wails-menus-linux.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `<../../../assets/blog-images>` tag with a local src path. Want to +use a cool font? Copy it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger an auto reload of the + application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-linux screenshot](../../../assets/blog-images/remote-linux.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/linux-build-cross-windows.webp) + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +Linux is **hard** to support. We expect there to be a number of quirks with the +beta. Please help us to help you by filing detailed bug reports! + +Finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors) whose support drives the project in many +ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: The v2 release isn't far off now! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md new file mode 100644 index 000000000..c72d7f566 --- /dev/null +++ b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md @@ -0,0 +1,198 @@ +--- +slug: blog/wails-v2-released +title: Wails v2 Released +authors: [leaanthony] +tags: [wails, v2] +date: 2022-09-22 +--- + +![montage screenshot](../../../assets/blog-images/montage.png) + +# It's here! + +Today marks the release of [Wails](https://wails.io) v2. It's been about 18 +months since the first v2 alpha and about a year from the first beta release. +I'm truly grateful to everyone involved in the evolution of the project. + +Part of the reason it took that long was due to wanting to get to some +definition of completeness before officially calling it v2. The truth is, +there's never a perfect time to tag a release - there's always outstanding +issues or "just one more" feature to squeeze in. What tagging an imperfect major +release does do, however, is to provide a bit of stability for users of the +project, as well as a bit of a reset for the developers. + +This release is more than I'd ever expected it to be. I hope it gives you as +much pleasure as it has given us to develop it. + +# What _is_ Wails? + +If you are unfamiliar with Wails, it is a project that enables Go programmers to +provide rich frontends for their Go programs using familiar web technologies. +It's a lightweight, Go alternative to Electron. Much more information can be +found on the [official site](https://wails.io/docs/introduction). + +# What's new? + +The v2 release is a huge leap forward for the project, addressing many of the +pain points of v1. If you have not read any of the blog posts on the Beta +releases for [macOS](/blog/wails-v2-beta-for-mac), +[Windows](/blog/wails-v2-beta-for-windows) or +[Linux](/blog/wails-v2-beta-for-linux), then I encourage you to do so as it +covers all the major changes in more detail. In summary: + +- Webview2 component for Windows that supports modern web standards and + debugging capabilities. +- [Dark / Light theme](https://wails.io/docs/reference/options#theme) + + [custom theming](https://wails.io/docs/reference/options#customtheme) on Windows. +- Windows now has no CGO requirements. +- Out-of-the-box support for Svelte, Vue, React, Preact, Lit & Vanilla project + templates. +- [Vite](https://vitejs.dev/) integration providing a hot-reload development + environment for your application. +- Native application + [menus](https://wails.io/docs/guides/application-development#application-menu) and + [dialogs](https://wails.io/docs/reference/runtime/dialog). +- Native window translucency effects for + [Windows](https://wails.io/docs/reference/options#windowistranslucent) and + [macOS](https://wails.io/docs/reference/options#windowistranslucent-1). Support for Mica & + Acrylic backdrops. +- Easily generate an [NSIS installer](https://wails.io/docs/guides/windows-installer) for + Windows deployments. +- A rich [runtime library](https://wails.io/docs/reference/runtime/intro) providing utility + methods for window manipulation, eventing, dialogs, menus and logging. +- Support for [obfuscating](https://wails.io/docs/guides/obfuscated) your application using + [garble](https://github.com/burrowers/garble). +- Support for compressing your application using [UPX](https://upx.github.io/). +- Automatic TypeScript generation of Go structs. More info + [here](https://wails.io/docs/howdoesitwork#calling-bound-go-methods). +- No extra libraries or DLLs are required to be shipped with your application. + For any platform. +- No requirement to bundle frontend assets. Just develop your application like + any other web application. + +# Credit & Thanks + +Getting to v2 has been a huge effort. There have been ~2.2K commits by 89 +contributors between the initial alpha and the release today, and many, many +more that have provided translations, testing, feedback and help on the +discussion forums as well as the issue tracker. I'm so unbelievably grateful to +each one of you. I'd also like to give an extra special thank you to all the +project sponsors who have provided guidance, advice and feedback. Everything you +do is hugely appreciated. + +There are a few people I'd like to give special mention to: + +Firstly, a **huge** thank you to [@stffabi](https://github.com/stffabi) who has +provided so many contributions which we all benefit from, as well as providing a +lot of support on many issues. He has provided some key features such as the +external dev server support which transformed our dev mode offering by allowing +us to hook into [Vite](https://vitejs.dev/)'s superpowers. It's fair to say that +Wails v2 would be a far less exciting release without his +[incredible contributions](https://github.com/wailsapp/wails/commits?author=stffabi&since=2020-01-04). +Thank you so much @stffabi! + +I'd also like to give a huge shout-out to +[@misitebao](https://github.com/misitebao) who has tirelessly been maintaining +the website, as well as providing Chinese translations, managing Crowdin and +helping new translators get up to speed. This is a hugely important task, and +I'm extremely grateful for all the time and effort put into this! You rock! + +Last, but not least, a huge thank you to Mat Ryer who has provided advice and +support during the development of v2. Writing xBar together using an early Alpha +of v2 was helpful in shaping the direction of v2, as well as give me an +understanding of some design flaws in the early releases. I'm happy to announce +that as of today, we will start to port xBar to Wails v2, and it will become the +flagship application for the project. Cheers Mat! + +# Lessons Learnt + +There are a number of lessons learnt in getting to v2 that will shape +development moving forward. + +## Smaller, Quicker, Focused Releases + +In the course of developing v2, there were many features and bug fixes that were +developed on an ad-hoc basis. This led to longer release cycles and were harder +to debug. Moving forward, we are going to create releases more often that will +include a reduced number of features. A release will involve updates to +documentation as well as thorough testing. Hopefully, these smaller, quicker, +focussed releases will lead to fewer regressions and better quality +documentation. + +## Encourage Engagement + +When starting this project, I wanted to immediately help everyone who had a +problem. Issues were "personal" and I wanted them resolved as quickly as +possible. This is unsustainable and ultimately works against the longevity of +the project. Moving forward, I will be giving more space for people to get +involved in answering questions and triaging issues. It would be good to get +some tooling to help with this so if you have any suggestions, please join in +the discussion [here](https://github.com/wailsapp/wails/discussions/1855). + +## Learning to say No + +The more people that engage with an Open Source project, the more requests there +will be for additional features that may or may not be useful to the majority of +people. These features will take an initial amount of time to develop and debug, +and incur an ongoing maintenance cost from that point on. I myself am the most +guilty of this, often wanting to "boil the sea" rather than provide the minimum +viable feature. Moving forward, we will need to say "No" a bit more to adding +core features and focus our energies on a way to empower developers to provide +that functionality themselves. We are looking seriously into plugins for this +scenario. This will allow anyone to extend the project as they see fit, as well +as providing an easy way to contribute towards the project. + +# Looking to the Future + +There are so many core features we are looking at to add to Wails in the next +major development cycle already. The +[roadmap](https://github.com/wailsapp/wails/discussions/1484) is full of +interesting ideas, and I'm keen to start work on them. One of the big asks has +been for multiple window support. It's a tricky one and to do it right, and we +may need to look at providing an alternative API, as the current one was not +designed with this in mind. Based on some preliminary ideas and feedback, I +think you'll like where we're looking to go with it. + +I'm personally very excited at the prospect of getting Wails apps running on +mobile. We already have a demo project showing that it is possible to run a +Wails app on Android, so I'm really keen to explore where we can go with this! + +A final point I'd like to raise is that of feature parity. It has long been a +core principle that we wouldn't add anything to the project without there being +full cross-platform support for it. Whilst this has proven to be (mainly) +achievable so far, it has really held the project back in releasing new +features. Moving forward, we will be adopting a slightly different approach: any +new feature that cannot be immediately released for all platforms will be +released under an experimental configuration or API. This allows early adopters +on certain platforms to try the feature and provide feedback that will feed into +the final design of the feature. This, of course, means that there are no +guarantees of API stability until it is fully supported by all the platforms it +can be supported on, but at least it will unblock development. + +# Final Words + +I'm really proud of what we've been able to achieve with the V2 release. It's +amazing to see what people have already been able to build using the beta +releases so far. Quality applications like [Varly](https://varly.app/), +[Surge](https://getsurge.io/) and [October](https://october.utf9k.net/). I +encourage you to check them out. + +This release was achieved through the hard work of many contributors. Whilst it +is free to download and use, it has not come about through zero cost. Make no +mistakes, this project has come at considerable cost. It has not only been my +time and the time of each and every contributor, but also the cost of absence +from friends and families of each of those people too. That's why I'm extremely +grateful for every second that has been dedicated to making this project happen. +The more contributors we have, the more this effort can be spread out and the +more we can achieve together. I'd like to encourage you all to pick one thing +that you can contribute, whether it is confirming someone's bug, suggesting a +fix, making a documentation change or helping out someone who needs it. All of +these small things have such a huge impact! It would be so awesome if you too +were part of the story in getting to v3. + +Enjoy! + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md new file mode 100644 index 000000000..a7d0d3b94 --- /dev/null +++ b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md @@ -0,0 +1,256 @@ +--- +slug: blog/the-road-to-wails-v3 +title: The Road to Wails v3 +authors: [leaanthony] +tags: [wails, v3] +date: 2023-01-17 +--- + +![multiwindow screenshot](../../../assets/blog-images/multiwindow.webp) + +# Introduction + +Wails is a project that simplifies the ability to write cross-platform desktop +applications using Go. It uses native webview components for the frontend (not +embedded browsers), bringing the power of the world's most popular UI system to +Go, whilst remaining lightweight. + +Version 2 was released on the 22nd of September 2022 and brought with it a lot +of enhancements including: + +- Live development, leveraging the popular Vite project +- Rich functionality for managing windows and creating menus +- Microsoft's WebView2 component +- Generation of Typescript models that mirror your Go structs +- Creating of NSIS Installer +- Obfuscated builds + +Right now, Wails v2 provides powerful tooling for creating rich, cross-platform +desktop applications. + +This blog post aims to look at where the project is at right now and what we can +improve on moving forward. + +# Where are we now? + +It's been incredible to see the popularity of Wails rising since the v2 release. +I'm constantly amazed by the creativity of the community and the wonderful +things that are being built with it. With more popularity, comes more eyes on +the project. And with that, more feature requests and bug reports. + +Over time, I've been able to identify some of the most pressing issues facing +the project. I've also been able to identify some of the things that are holding +the project back. + +## Current issues + +I've identified the following areas that I feel are holding the project back: + +- The API +- Bindings generation +- The Build System + +### The API + +The API to build a Wails application currently consists of 2 parts: + +- The Application API +- The Runtime API + +The Application API famously has only 1 function: `Run()` which takes a heap of +options which govern how the application will work. Whilst this is very simple +to use, it is also very limiting. It is a "declarative" approach which hides a +lot of the underlying complexity. For instance, there is no handle to the main +window, so you can't interact with it directly. For that, you need to use the +Runtime API. This is a problem when you start to want to do more complex things +like create multiple windows. + +The Runtime API provides a lot of utility functions for the developer. This +includes: + +- Window management +- Dialogs +- Menus +- Events +- Logs + +There are a number of things I am not happy with the Runtime API. The first is +that it requires a "context" to be passed around. This is both frustrating and +confusing for new developers who pass in a context and then get a runtime error. + +The biggest issue with the Runtime API is that it was designed for applications +that only use a single window. Over time, the demand for multiple windows has +grown and the API is not well suited to this. + +### Thoughts on the v3 API + +Wouldn't it be great if we could do something like this? + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +This programmatic approach is far more intuitive and allows the developer to +interact with the application elements directly. All current runtime methods for +windows would simply be methods on the window object. For the other runtime +methods, we could move them to the application object like so: + +```go +app := wails.NewApplication(options.App{}) +app.NewInfoDialog(options.InfoDialog{}) +app.Log.Info("Hello World") +``` + +This is a much more powerful API which will allow for more complex applications +to be built. It also allows for the creation of multiple windows, +[the most up-voted feature on GitHub](https://github.com/wailsapp/wails/issues/1480): + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + myWindow2 := app.NewWindow(options.Window{}) + myWindow2.SetTitle("My Window 2") + myWindow2.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +### Bindings generation + +One of the key features of Wails is generating bindings for your Go methods so +they may be called from Javascript. The current method for doing this is a bit +of a hack. It involves building the application with a special flag and then +running the resultant binary which uses reflection to determine what has been +bound. This leads to a bit of a chicken and egg situation: You can't build the +application without the bindings and you can't generate the bindings without +building the application. There are many ways around this but the best one would +be not to use this approach at all. + +There were a number of attempts at writing a static analyser for Wails projects +but they didn't get very far. In more recent times, it has become slightly +easier to do this with more material available on the subject. + +Compared to reflection, the AST approach is much faster however it is +significantly more complicated. To start with, we may need to impose certain +constraints on how to specify bindings in the code. The goal is to support the +most common use cases and then expand it later on. + +### The Build System + +Like the declarative approach to the API, the build system was created to hide +the complexities of building a desktop application. When you run `wails build`, +it does a lot of things behind the scenes: + +- Builds the backend binary for bindings and generates the bindings +- Installs the frontend dependencies +- Builds the frontend assets +- Determines if the application icon is present and if so, embeds it +- Builds the final binary +- If the build is for `darwin/universal` it builds 2 binaries, one for + `darwin/amd64` and one for `darwin/arm64` and then creates a fat binary using + `lipo` +- If compression is required, it compresses the binary with UPX +- Determines if this binary is to be packaged and if so: + - Ensures the icon and application manifest are compiled into the binary + (Windows) + - Builds out the application bundle, generates the icon bundle and copies it, + the binary and Info.plist to the application bundle (Mac) +- If an NSIS installer is required, it builds it + +This entire process, whilst very powerful, is also very opaque. It is very +difficult to customise it and it is very difficult to debug. + +To address this in v3, I would like to move to a build system that exists +outside of Wails. After using [Task](https://taskfile.dev/) for a while, I am a +big fan of it. It is a great tool for configuring build systems and should be +reasonably familiar to anyone who has used Makefiles. + +The build system would be configured using a `Taskfile.yml` file which would be +generated by default with any of the supported templates. This would have all of +the steps required to do all the current tasks, such as building or packaging +the application, allowing for easy customisation. + +There will be no external requirement for this tooling as it would form part of +the Wails CLI. This means that you can still use `wails build` and it will do +all the things it does today. However, if you want to customise the build +process, you can do so by editing the `Taskfile.yml` file. It also means you can +easily understand the build steps and use your own build system if you wish. + +The missing piece in the build puzzle is the atomic operations in the build +process, such as icon generation, compression and packaging. To require a bunch +of external tooling would not be a great experience for the developer. To +address this, the Wails CLI will provide all these capabilities as part of the +CLI. This means that the builds still work as expected, with no extra external +tooling, however you can replace any step of the build with any tool you like. + +This will be a much more transparent build system which will allow for easier +customisation and address a lot of the issues that have been raised around it. + +## The Payoff + +These positive changes will be a huge benefit to the project: + +- The new API will be much more intuitive and will allow for more complex + applications to be built. +- Using static analysis for bindings generation will be much faster and reduce a + lot of the complexity around the current process. +- Using an established, external build system will make the build process + completely transparent, allowing for powerful customisation. + +Benefits to the project maintainers are: + +- The new API will be much easier to maintain and adapt to new features and + platforms. +- The new build system will be much easier to maintain and extend. I hope this + will lead to a new ecosystem of community driven build pipelines. +- Better separation of concerns within the project. This will make it easier to + add new features and platforms. + +## The Plan + +A lot of the experimentation for this has already been done and it's looking +good. There is no current timeline for this work but I'm hoping by the end of Q1 +2023, there will be an alpha release for Mac to allow the community to test, +experiment with and provide feedback. + +## Summary + +- The v2 API is declarative, hides a lot from the developer and not suitable for + features such as multiple windows. A new API will be created which will be + simpler, intuitive and more powerful. +- The build system is opaque and difficult to customise so we will move to an + external build system which will open it all up. +- The bindings generation is slow and complex so we will move to static analysis + which will remove a lot of the complexity the current method has. + +There has been a lot of work put into the guts of v2 and it's solid. It's now +time to address the layer on top of it and make it a much better experience for +the developer. + +I hope you are as excited about this as I am. I'm looking forward to hearing +your thoughts and feedback. + +Regards, + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! + +PPS: Yes, that's a genuine screenshot of a multi-window application built with +Wails. It's not a mockup. It's real. It's awesome. It's coming soon. diff --git a/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md new file mode 100644 index 000000000..0cba0baa7 --- /dev/null +++ b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md @@ -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.* \ No newline at end of file diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx new file mode 100644 index 000000000..f25ad4e81 --- /dev/null +++ b/docs/src/content/docs/changelog.mdx @@ -0,0 +1,681 @@ +--- +title: Changelog +--- + + +Legend: +- ๏ฃฟ - macOS +- โŠž - Windows +- ๐Ÿง - Linux + +/*-- +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] + +## 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) +- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Better panic handling by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031) +- More documentation by [@leaanthony](https://github.com/leaanthony) +- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony) +- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony) +- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304) +- Report package path in binding generator warnings about unsupported types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for generic aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for `omitzero` JSON flag by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- 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) +- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony) +- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Pass build flags to binding generator by [@fbbdev](https://github.com/fbbdev) in [#4023](https://github.com/wailsapp/wails/pull/4023) +- Change paths in windows Taskfile to forward slashes to ensure it works on non-Windows platforms by [@leaanthony](https://github.com/leaanthony) +- Mac + Mac JS events now fixed by [@leaanthony](https://github.com/leaanthony) +- Fixed event deadlock for macOS by [@leaanthony](https://github.com/leaanthony) +- Fixed a `Parameter incorrect` error in Window initialisation on Windows when HTML provided but no JS by [@leaanthony](https://github.com/leaanthony) +- Fixed size of response prefix used for content type sniffing in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed handling of non-404 responses on root index path in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed undefined behaviour in binding generator when testing properties of generic types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for models when underlying type has not the same properties as named wrapper by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for map key types and preprocessing by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for structs that implement marshaler interfaces by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed detection of type cycles involving generic types in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed invalid references to unexported models in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Moved injected code to the end of service files by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed handling of errors from file close operations in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- 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 + +- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Removed `nanoid` dependency by [@leaanthony](https://github.com/leaanthony) +- Updated Window example for mica/acrylic/tabbed window styles by [@leaanthony](https://github.com/leaanthony) +- 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 + +### Added + +- `app.OpenFileManager(path string, selectFile bool)` to open the system file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01) [@rcalixte](https://github.com/rcalixte) +- New `-git` flag for `wails3 init` command by [@leaanthony](https://github.com/leaanthony) +- New `wails3 generate webview2bootstrapper` command by [@leaanthony](https://github.com/leaanthony) +- Added `init()` method in runtime to allow manual initialisation of the runtime by [@leaanthony](https://github.com/leaanthony) +- Added `WindowDidMoveDebounceMS` option to Window's WindowOptions by [@leaanthony](https://github.com/leaanthony) +- Added Single Instance feature by [@leaanthony](https://github.com/leaanthony). Based on the [v2 PR](https://github.com/wailsapp/wails/pull/2951) by @APshenkin. +- `wails3 generate template` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 releasenotes` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 update cli` command by [@leaanthony](https://github.com/leaanthony) +- `-clean` option for `wails3 generate bindings` command by [@leaanthony](https://github.com/leaanthony) +- Allow for aarch64 (arm64) AppImage Linux builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) + +### Fixed + +- Fixed min/max width options for linux by @atterpac in [#3979](https://github.com/wailsapp/wails/pull/3979) +- Typescript templates types definitions via npm version bump by @atterpac in [#3966](https://github.com/wailsapp/wails/pull/3966) +- Fix Sveltekit template CSS referance by @atterpac in [#3945](https://github.com/wailsapp/wails/pull/3945) +- Ensure key callbacks in window run() are called on the main thread by [@leaanthony](https://github.com/leaanthony) +- Fix dialog directory chooser examples by [@leaanthony](https://github.com/leaanthony) +- Created new Chinese error page when index.html is missing by [@leaanthony](https://github.com/leaanthony) +- ๏ฃฟ Ensure `windowDidBecomeKey` callback is running on main thread by [@leaanthony](https://github.com/leaanthony) +- ๏ฃฟ Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +- ๏ฃฟ Improved window destroying logic by [@leaanthony](https://github.com/leaanthony) +- ๏ฃฟ Fix window position logic when attached to system trays by [@leaanthony](https://github.com/leaanthony) +- ๏ฃฟ Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +- Fix event handling by [@leaanthony](https://github.com/leaanthony) +- Fixed window shutdown logic by [@leaanthony](https://github.com/leaanthony) +- Common taskfile now defaults to generating Typescript bindings for Typescript templates by [@leaanthony](https://github.com/leaanthony) +- Fix Close application on WM_CLOSE message when no windows are open/systray only by [@mmalcek](https://github.com/mmalcek) in [#3990](https://github.com/wailsapp/wails/pull/3990) +- Fixed garble build by @5aaee9 in [#3192](https://github.com/wailsapp/wails/pull/3192) +- Fixed windows nsis builds by [@leaanthony](https://github.com/leaanthony) + +### Changed + +- Moved build assets to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Moved and renamed Taskfiles to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Created a much better experience when `index.html` is missing by [@leaanthony](https://github.com/leaanthony) +- [Windows] Improved performance of minimise and restore by [@leaanthony](https://github.com/leaanthony). Based on original [PR](https://github.com/wailsapp/wails/pull/3955) by [562589540](https://github.com/562589540) +- Removed `ShouldClose` option (Register a hook for events.Common.WindowClosing instead) by [@leaanthony](https://github.com/leaanthony) +- [Windows] Reduced flicker when opening a window by [@leaanthony](https://github.com/leaanthony) +- Removed `Window.Destroy` as this was intended to be an internal function by [@leaanthony](https://github.com/leaanthony) +- Renamed `WindowClose` events to `WindowClosing` by [@leaanthony](https://github.com/leaanthony) +- Frontend builds now use vite environment "development" or "production" depending on build type by [@leaanthony](https://github.com/leaanthony) +- Update to go-webview2 v1.19 by [@leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.8.3 - 2024-12-07 + +### Changed + +- Ensure fork of taskfile is used by @leaanthony + +## v3.0.0-alpha.8.2 - 2024-12-07 + +### Changed + +- Update fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8.1 - 2024-12-07 + +### Changed + +- Using fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8 - 2024-12-06 + +### Added + +- Added hyperlink for sponsor by @ansxuman in [#3958](https://github.com/wailsapp/wails/pull/3958) +- Support of linux packaging of deb,rpm, and arch linux packager builds by + @atterpac in [#3909](https://github.com/wailsapp/wails/3909) +- Added Support for darwin universal builds and packages by + [ansxuman](https://github.com/ansxuman) in + [#3902](https://github.com/wailsapp/wails/pull/3902) +- Events documentation to the website by + [atterpac](https://github.com/atterpac) in + [#3867](https://github.com/wailsapp/wails/pull/3867) +- Templates for sveltekit and sveltekit-ts that are set for non-SSR development + by [atterpac](https://github.com/atterpac) in + [#3829](https://github.com/wailsapp/wails/pull/3829) +- Update build assets using new `wails3 update build-assets` command by + [leaanthony](https://github.com/leaanthony) +- Example to test the HTML Drag and Drop API by + [FerroO2000](https://github.com/FerroO2000) in + [#3856](https://github.com/wailsapp/wails/pull/3856) +- File Association support by [leaanthony](https://github.com/leaanthony) in + [#3873](https://github.com/wailsapp/wails/pull/3873) +- New `wails3 generate runtime` command by + [leaanthony](https://github.com/leaanthony) +- New `InitialPosition` option to specify if the window should be centered or + positioned at the given X/Y location by + [leaanthony](https://github.com/leaanthony) in + [#3885](https://github.com/wailsapp/wails/pull/3885) +- Add `Path` & `Paths` methods to `application` package by + [ansxuman](https://github.com/ansxuman) and + [leaanthony](https://github.com/leaanthony) in + [#3823](https://github.com/wailsapp/wails/pull/3823) +- Added `GeneralAutofillEnabled` and `PasswordAutosaveEnabled` Windows options + by [leaanthony](https://github.com/leaanthony) in + [#3766](https://github.com/wailsapp/wails/pull/3766) +- Added the ability to retrieve the window calling a service method by + [leaanthony](https://github.com/leaanthony) in + [#3888](https://github.com/wailsapp/wails/pull/3888) +- Added `EnabledFeatures` and `DisabledFeatures` options for Webview2 by + [leaanthony](https://github.com/leaanthony). +- + +### Changed + +- `service.OnStartup` now shutdowns the application on error and runs + `service.OnShutdown`for any prior services that started by @atterpac in + [#3920](https://github.com/wailsapp/wails/pull/3920) +- Refactored systray click messaging to better align with user interactions by + @atterpac in [#3907](https://github.com/wailsapp/wails/pull/3907) +- Asset embed to include `all:frontend/dist` to support frameworks that generate + subfolders by @atterpac in + [#3887](https://github.com/wailsapp/wails/pull/3887) +- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in + [#3748](https://github.com/wailsapp/wails/pull/3748) +- Upgrade to `go-webview2` v1.0.16 by + [leaanthony](https://github.com/leaanthony) +- Fixed `Screen` type to include `ID` not `Id` by + [etesam913](https://github.com/etesam913) in + [#3778](https://github.com/wailsapp/wails/pull/3778) +- Update `go.mod.tmpl` wails version to support `application.ServiceOptions` by + [northes](https://github.com/northes) in + [#3836](https://github.com/wailsapp/wails/pull/3836) +- Fixed service name determination by [windom](https://github.com/windom/) in + [#3827](https://github.com/wailsapp/wails/pull/3827) +- mkdocs serve now uses docker by [leaanthony](https://github.com/leaanthony) +- Consolidated dev config into `config.yml` by + [leaanthony](https://github.com/leaanthony) +- Systray dialog now defaults to the application icon if available (Windows) by + [@leaanthony](https://github.com/leaanthony) +- Better reporting of GPU + Memory for macOS by + [@leaanthony](https://github.com/leaanthony) +- Removed `WebviewGpuIsDisabled` and `EnableFraudulentWebsiteWarnings` + (superseded by `EnabledFeatures` and `DisabledFeatures` options) by + [leaanthony](https://github.com/leaanthony) + +### Fixed + +- Fixed deadlock in Linux dialog for multiple selections caused by unclosed + channel variable by @michael-freling in + [#3925](https://github.com/wailsapp/wails/pull/3925) +- Fixed cross-platform cleanup for .syso files during Windows build by + [ansxuman](https://github.com/ansxuman) in + [#3924](https://github.com/wailsapp/wails/pull/3924) +- Fixed amd64 appimage compile by @atterpac in + [#3898](https://github.com/wailsapp/wails/pull/3898) +- Fixed build assets update by @ansxuman in + [#3901](https://github.com/wailsapp/wails/pull/3901) +- Fixed Linux systray `OnClick` and `OnRightClick` implementation by @atterpac + in [#3886](https://github.com/wailsapp/wails/pull/3886) +- Fixed `AlwaysOnTop` not working on Mac by + [leaanthony](https://github.com/leaanthony) in + [#3841](https://github.com/wailsapp/wails/pull/3841) +- ๏ฃฟ Fixed `application.NewEditMenu` including a duplicate + `PasteAndMatchStyle` role in the edit menu on Darwin by + [johnmccabe](https://github.com/johnmccabe) in + [#3839](https://github.com/wailsapp/wails/pull/3839) +- ๐Ÿง Fixed aarch64 compilation + [#3840](https://github.com/wailsapp/wails/issues/3840) in + [#3854](https://github.com/wailsapp/wails/pull/3854) by + [kodflow](https://github.com/kodflow) +- โŠž Fixed radio group menu items by + [@leaanthony](https://github.com/leaanthony) +- Fix error on building runnable .app on MacOS when 'name' and 'outputfilename' + are different. by @nickisworking in + [#3789](https://github.com/wailsapp/wails/pull/3789) + +## v3.0.0-alpha.7 - 2024-09-18 + +### Added + +- โŠž New DIP system for Enhanced High DPI Monitor Support by + [mmghv](https://github.com/mmghv) in + [#3665](https://github.com/wailsapp/wails/pull/3665) +- โŠž Window class name option by [windom](https://github.com/windom/) in + [#3682](https://github.com/wailsapp/wails/pull/3682) +- Services have been expanded to provide plugin functionality. By + [atterpac](https://github.com/atterpac) and + [leaanthony](https://github.com/leaanthony) in + [#3570](https://github.com/wailsapp/wails/pull/3570) + +### Changed + +- Events API change: `On`/`Emit` -> user events, `OnApplicationEvent` -> + Application Events `OnWindowEvent` -> Window Events, by + [leaanthony](https://github.com/leaanthony) +- Fix for Events API on Linux by [TheGB0077](https://github.com/TheGB0077) in + [#3734](https://github.com/wailsapp/wails/pull/3734) +- [CI] improvements to actions & enable to run actions also in forks and + branches prefixed with `v3/` or `v3-` by + [stendler](https://github.com/stendler) in + [#3747](https://github.com/wailsapp/wails/pull/3747) + +### Fixed + +- Fixed bug with usage of customEventProcessor in drag-n-drop example by + [etesam913](https://github.com/etesam913) in + [#3742](https://github.com/wailsapp/wails/pull/3742) +- ๐Ÿง Fixed linux compile error introduced by IgnoreMouseEvents addition by + [atterpac](https://github.com/atterpac) in + [#3721](https://github.com/wailsapp/wails/pull/3721) +- โŠž Fixed syso icon file generation bug by + [atterpac](https://github.com/atterpac) in + [#3675](https://github.com/wailsapp/wails/pull/3675) +- ๐Ÿง Fix to run natively in wayland incorporated from + [#1811](https://github.com/wailsapp/wails/pull/1811) in + [#3614](https://github.com/wailsapp/wails/pull/3614) by + [@stendler](https://github.com/stendler) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- โŠž Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- โŠž Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Major menu item refactor and event handling. Mainly improves macOS for now. By + [leaanthony](https://github.com/leaanthony) +- Fix tests after plugins and event refactor in + [#3746](https://github.com/wailsapp/wails/pull/3746) by + [@stendler](https://github.com/stendler) +- โŠž Fixed `Failed to unregister class Chrome_WidgetWin_0` warning. By + [leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.6 - 2024-07-30 + +### Fixed + +- Module issues + +## v3.0.0-alpha.5 - 2024-07-30 + +### Added + +- ๐Ÿง WindowDidMove / WindowDidResize events in + [#3580](https://github.com/wailsapp/wails/pull/3580) +- โŠž WindowDidResize event in + [#3580](https://github.com/wailsapp/wails/pull/3580) +- ๏ฃฟ add Event ApplicationShouldHandleReopen to be able to handle dock + icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991) +- ๏ฃฟ add getPrimaryScreen/getScreens to impl by @tmclane in + [#2618](https://github.com/wailsapp/wails/pull/2618) +- ๏ฃฟ 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) +- ๐Ÿง add onKeyPress logic to convert linux keypress into an accelerator + @[Atterpac](https://github.com/Atterpac) + in[#3022](https://github.com/wailsapp/wails/pull/3022]) +- ๐Ÿง 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) +- โŠž add `SetIgnoreMouseEvents` for already created window by + [@bruxaodev](https://github.com/bruxaodev) in + [#3667](https://github.com/wailsapp/wails/pull/3667) +- ๏ฃฟ Add ability to set a window's stacking level (order) by + [@OlegGulevskyy](https://github.com/OlegGulevskyy) in + [#3674](https://github.com/wailsapp/wails/pull/3674) + +### Fixed + +- Fixed resize event messaging by [atterpac](https://github.com/atterpac) in + [#3606](https://github.com/wailsapp/wails/pull/3606) +- ๐Ÿง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 + [mmghv](https://github.com/mmghv) in + [#2750](https://github.com/wailsapp/wails/pull/2750). +- Fixed default context menus by [mmghv](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) +- Fix missing MicrosoftEdgeWebview2Setup.exe. Thanks to + [@robin-samuel](https://github.com/robin-samuel). +- Fix random crash on linux due to window ID handling by @leaanthony. Based on + PR [#3466](https://github.com/wailsapp/wails/pull/3622) by + [@5aaee9](https://github.com/5aaee9). +- Fix systemTray.setIcon crashing on Linux by + [@windom](https://github.com/windom/) in + [#3636](https://github.com/wailsapp/wails/pull/3636). +- Fix Ensure Window Frame is Applied on First Call in `setFrameless` Function on + Windows by [@bruxaodev](https://github.com/bruxaodev/) in + [#3691](https://github.com/wailsapp/wails/pull/3691). + +### Changed + +- Renamed `AbsolutePosition()` to `Position()` by + [mmghv](https://github.com/mmghv) in + [#3611](https://github.com/wailsapp/wails/pull/3611) +- 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) +- Disable spinner on Non-Terminal or CI Environment by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) in + [#3574](https://github.com/wailsapp/wails/pull/3574) diff --git a/docs/src/content/docs/community/links.md b/docs/src/content/docs/community/links.md new file mode 100644 index 000000000..9507991dd --- /dev/null +++ b/docs/src/content/docs/community/links.md @@ -0,0 +1,27 @@ +--- +title: Links +--- + +This page serves as a list for community related links. + +:::tip[How to Submit a Link] + +You can click the `Edit page` at the bottom of this page to submit a PR. + +::: + +## Awesome Wails + +The [definitive list](https://github.com/wailsapp/awesome-wails) of links +related to Wails. + +## Support Channels + +- [Wails Discord Server](https://discord.gg/bdj28QNHmT) +- [Github Issues](https://github.com/wailsapp/wails/issues) + +## Social Media + +- [Twitter](https://x.com/wailsapp) +- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - + Group number: 1067173054 diff --git a/docs/src/content/docs/community/showcase/_template.md b/docs/src/content/docs/community/showcase/_template.md new file mode 100644 index 000000000..2ad8aab68 --- /dev/null +++ b/docs/src/content/docs/community/showcase/_template.md @@ -0,0 +1,13 @@ +--- +title: My Project +--- + + + +![My Project](../../../../assets/showcase-images/my-project.webp) + + + + + +[My Project](https://my-project.com) diff --git a/docs/src/content/docs/community/showcase/bulletinboard.md b/docs/src/content/docs/community/showcase/bulletinboard.md new file mode 100644 index 000000000..58d24b97f --- /dev/null +++ b/docs/src/content/docs/community/showcase/bulletinboard.md @@ -0,0 +1,16 @@ +--- +title: BulletinBoard +--- + +![BulletinBoard](../../../../assets/showcase-images/bboard.webp) + +The [BulletinBoard](https://github.com/raguay/BulletinBoard) application is a +versital message board for static messages or dialogs to get information from +the user for a script. It has a TUI for creating new dialogs that can latter be +used to get information from the user. It's design is to stay running on your +system and show the information as needed and then hide away. I have a process +for watching a file on my system and sending the contents to BulletinBoard when +changed. It works great with my workflows. T here is also an +[Alfred workflow](https://github.com/raguay/MyAlfred/blob/master/Alfred%205/EmailIt.alfredworkflow) +for sending information to the program. The workflow is also for working with +[EmailIt](https://github.com/raguay/EmailIt). diff --git a/docs/src/content/docs/community/showcase/cfntracker.md b/docs/src/content/docs/community/showcase/cfntracker.md new file mode 100644 index 000000000..dc9c030f3 --- /dev/null +++ b/docs/src/content/docs/community/showcase/cfntracker.md @@ -0,0 +1,36 @@ +--- +title: CFN Tracker +--- + +![CFN Tracker](../../../../assets/showcase-images/cfntracker.webp) + +[CFN Tracker](https://github.com/williamsjokvist/cfn-tracker) - Track any Street +Fighter 6 or V CFN profile's live matches. Check +[the website](https://cfn.williamsjokvist.se/) to get started. + +## Features + +- Real-time match tracking +- Storing match logs and statistics +- Support for displaying live stats to OBS via Browser Source +- Support for both SF6 and SFV +- Ability for users to create their own OBS Browser themes with CSS + +### Major tech used alongside Wails + +- [Task](https://github.com/go-task/task) - wrapping the Wails CLI to make + common commands easy to use +- [React](https://github.com/facebook/react) - chosen for its rich ecosystem + (radix, framer-motion) +- [Bun](https://github.com/oven-sh/bun) - used for its fast dependency + resolution and build-time +- [Rod](https://github.com/go-rod/rod) - headless browser automation for + authentication and polling changes +- [SQLite](https://github.com/mattn/go-sqlite3) - used for storing matches, + sessions and profiles +- [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) - + a http stream to send tracking updates to OBS browser sources +- [i18next](https://github.com/i18next/) - with backend connector to serve + localization objects from the Go layer +- [xstate](https://github.com/statelyai/xstate) - state machines for auth + process and tracking diff --git a/docs/src/content/docs/community/showcase/clave.md b/docs/src/content/docs/community/showcase/clave.md new file mode 100644 index 000000000..9c405e616 --- /dev/null +++ b/docs/src/content/docs/community/showcase/clave.md @@ -0,0 +1,18 @@ +--- +title: Clave +--- + +![Clave](../../../../assets/showcase-images/clave.png) + +Key Features + +- ๐ŸŽจ Simple, intuitive design for hassle-free experience +- โœ… Add accounts easily via manual entry or QR code image import. +- ๐Ÿ”’ End-to-end encryption with PIN or Touch ID(macOS only). +- ๐Ÿ’ป Available for macOS, Windows, and Linux +- โšก Quick access from system tray for convenience +- ๐Ÿ“‚ Easily backup and restore your profiles + +Try Clave Today! + +๐Ÿ’ป [https://clave.rocks](https://clave.rocks) diff --git a/docs/src/content/docs/community/showcase/emailit.md b/docs/src/content/docs/community/showcase/emailit.md new file mode 100644 index 000000000..6afc1da5e --- /dev/null +++ b/docs/src/content/docs/community/showcase/emailit.md @@ -0,0 +1,14 @@ +--- +title: EmailIt +--- + +![EmailIt](../../../../assets/showcase-images/emailit.webp) + +[EmailIt](https://github.com/raguay/EmailIt/) is a Wails 2 program that is a +markdown based email sender only with nine notepads, scripts to manipulate the +text, and templates. It also has a scripts terminal to run scripts in EmailIt on +files in your system. The scripts and templates can be used from the commandline +itself or with the Alfred, Keyboard Maestro, Dropzone, or PopClip extensions. It +also supports scripts and themes downloaded form GitHub. Documentation is not +complete, but the programs works. Itโ€™s built using Wails2 and Svelte, and the +download is a universal macOS application. diff --git a/docs/src/content/docs/community/showcase/encrypteasy.md b/docs/src/content/docs/community/showcase/encrypteasy.md new file mode 100644 index 000000000..3a098fac9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/encrypteasy.md @@ -0,0 +1,16 @@ +--- +title: EncryptEasy +--- + +![EncryptEasy](../../../../assets/showcase-images/encrypteasy.webp) + +**[EncryptEasy](https://www.encrypteasy.app) is a simple and easy to use PGP +encryption tool, managing all your and your contacts keys. Encryption should be +simple. Developed with Wails.** + +Encrypting messages using PGP is the industry standard. Everyone has a private +and a public key. Your private key, well, needs to be kept private so only you +can read messages. Your public key is distributed to anyone who wants to send +you secret, encrypted messages. Managing keys, encrypting messages and +decrypting messages should be a smooth experience. EncryptEasy is all about +making it easy. diff --git a/docs/src/content/docs/community/showcase/espstudio.md b/docs/src/content/docs/community/showcase/espstudio.md new file mode 100644 index 000000000..0ac90a827 --- /dev/null +++ b/docs/src/content/docs/community/showcase/espstudio.md @@ -0,0 +1,9 @@ +--- +title: ESP Studio +--- + +![ESP Studio](../../../../assets/showcase-images/esp-studio.png) + +[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop, +Cloud, and Embedded software for controlling ESP/Arduino devices, and building +complex IOT workflows and control systems diff --git a/docs/src/content/docs/community/showcase/filehound.md b/docs/src/content/docs/community/showcase/filehound.md new file mode 100644 index 000000000..627007f97 --- /dev/null +++ b/docs/src/content/docs/community/showcase/filehound.md @@ -0,0 +1,29 @@ +--- +title: FileHound Export Utility +--- + +![FileHound Export Utility](../../../../assets/showcase-images/filehound.webp) + +[FileHound Export Utility](https://www.filehound.co.uk/) FileHound is a cloud +document management platform made for secure file retention, business process +automation and SmartCapture capabilities. + +The FileHound Export Utility allows FileHound Administrators the ability to run +a secure document and data extraction tasks for alternative back-up and recovery +purposes. This application will download all documents and/or meta data saved in +FileHound based on the filters you choose. The metadata will be exported in both +JSON and XML formats. + +Backend built with: + +- Go 1.15 +- Wails 1.11.0 +- go-sqlite3 1.14.6 +- go-linq 3.2 + +Frontend with: + +- Vue 2.6.11 +- Vuex 3.4.0 +- TypeScript +- Tailwind 1.9.6 diff --git a/docs/src/content/docs/community/showcase/hiposter.md b/docs/src/content/docs/community/showcase/hiposter.md new file mode 100644 index 000000000..08acfa832 --- /dev/null +++ b/docs/src/content/docs/community/showcase/hiposter.md @@ -0,0 +1,8 @@ +--- +title: hiposter +--- + +![hiposter](../../../../assets/showcase-images/hiposter.webp) + +[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API +testing client tool. Based on Wails, Go and sveltejs. diff --git a/docs/src/content/docs/community/showcase/index.mdx b/docs/src/content/docs/community/showcase/index.mdx new file mode 100644 index 000000000..e1da3f75d --- /dev/null +++ b/docs/src/content/docs/community/showcase/index.mdx @@ -0,0 +1,189 @@ +--- +title: Overview +sidebar: + order: 1 +--- + +import { ShowcaseImage } from "starlight-showcases"; +import { Steps } from "@astrojs/starlight/components"; + +:::tip[See how to add your project] + +Check out the +[How to add my project in showcase](#how-to-add-my-project-in-showcase) section. + +::: + + + +## How to add my project in showcase + + + +1. Make a fork of the repository. +2. Add the image(s) under `docs/src/assets/showcase-images` folder. +3. Make a copy of the `_template.md` file under + `docs/src/content/docs/community/showcase` folder. +4. Rename the copied file to the name of your project. (Name should not start + with `_`)) +5. Update the title, image, link and content of the file. +6. Add it on the above list in + `docs/src/content/docs/community/showcase/index.mdx`. +7. Submit a PR. + + diff --git a/docs/src/content/docs/community/showcase/mchat.md b/docs/src/content/docs/community/showcase/mchat.md new file mode 100644 index 000000000..444aed343 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mchat.md @@ -0,0 +1,8 @@ +--- +title: Mchat +--- + +![Mchat](../../../../assets/showcase-images/mchat.png) + +[Official page](https://marcio199226.github.io/mchat-site/public/) Fully +anonymous end2end encrypted chat. diff --git a/docs/src/content/docs/community/showcase/minecraftupdater.md b/docs/src/content/docs/community/showcase/minecraftupdater.md new file mode 100644 index 000000000..57cd80e30 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minecraftupdater.md @@ -0,0 +1,10 @@ +--- +title: Minecraft Updater +--- + +![Minecraft Updater](../../../../assets/showcase-images/minecraft-mod-updater.webp) + +[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) is a +utility tool to update and synchronize Minecraft mods for your userbase. Itโ€™s +built using Wails2 and React with [antd](https://ant.design/) as frontend +framework. diff --git a/docs/src/content/docs/community/showcase/minesweeper-xp.md b/docs/src/content/docs/community/showcase/minesweeper-xp.md new file mode 100644 index 000000000..9ca30ed55 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minesweeper-xp.md @@ -0,0 +1,8 @@ +--- +title: Minesweeper XP +--- + +![Minesweeper XP](../../../../assets/showcase-images/minesweeper-xp.webp) + +[Minesweeper-XP](https://git.new/Minesweeper-XP) allows you to experience the +classic Minesweeper XP (+ 98 and 3.1) on macOS, Windows, and Linux! diff --git a/docs/src/content/docs/community/showcase/modalfilemanager.md b/docs/src/content/docs/community/showcase/modalfilemanager.md new file mode 100644 index 000000000..7a7748d4d --- /dev/null +++ b/docs/src/content/docs/community/showcase/modalfilemanager.md @@ -0,0 +1,21 @@ +--- +title: Modal File Manager +--- + +![Modal File Manager](../../../../assets/showcase-images/modalfilemanager.webp) + +[Modal File Manager](https://github.com/raguay/ModalFileManager) is a dual pane +file manager using web technologies. My original design was based on NW.js and +can be found [here](https://github.com/raguay/ModalFileManager-NWjs). This +version uses the same Svelte based frontend code (but it has be greatly modified +since the departure from NW.js), but the backend is a +[Wails 2](https://wails.io/) implementation. By using this implementation, I no +longer use command line `rm`, `cp`, etc. commands, but a git install has to be +on the system to download themes and extensions. It is fully coded using Go and +runs much faster than the previous versions. + +This file manager is designed around the same principle as Vim: a state +controlled keyboard actions. The number of states isn't fixed, but very +programmable. Therefore, an infinite number of keyboard configurations can be +created and used. This is the main difference from other file managers. There +are themes and extensions available to download from GitHub. diff --git a/docs/src/content/docs/community/showcase/mollywallet.md b/docs/src/content/docs/community/showcase/mollywallet.md new file mode 100644 index 000000000..930aebd75 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mollywallet.md @@ -0,0 +1,9 @@ +--- +title: Molley Wallet +--- + +![Molly Wallet](../../../../assets/showcase-images/mollywallet.webp) + +[Molly Wallet](https://github.com/grvlle/constellation_wallet/) the official +$DAG wallet of the Constellation Network. It'll let users interact with the +Hypergraph Network in various ways, not limited to producing $DAG transactions. diff --git a/docs/src/content/docs/community/showcase/october.md b/docs/src/content/docs/community/showcase/october.md new file mode 100644 index 000000000..60a29e91b --- /dev/null +++ b/docs/src/content/docs/community/showcase/october.md @@ -0,0 +1,16 @@ +--- +title: October +--- + +![October](../../../../assets/showcase-images/october.webp) + +[October](https://october.utf9k.net) is a small Wails application that makes it +really easy to extract highlights from +[Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) and then forward +them to [Readwise](https://readwise.io). + +It has a relatively small scope with all platform versions weighing in under +10MB, and that's without enabling [UPX compression](https://upx.github.io/)! + +In contrast, the author's previous attempts with Electron quickly bloated to +several hundred megabytes. diff --git a/docs/src/content/docs/community/showcase/optimus.md b/docs/src/content/docs/community/showcase/optimus.md new file mode 100644 index 000000000..6128258b2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/optimus.md @@ -0,0 +1,9 @@ +--- +title: Optimus +--- + +![Optimus](../../../../assets/showcase-images/optimus.webp) + +[Optimus](https://github.com/splode/optimus) is a desktop image optimization +application. It supports conversion and compression between WebP, JPEG, and PNG +image formats. diff --git a/docs/src/content/docs/community/showcase/portfall.md b/docs/src/content/docs/community/showcase/portfall.md new file mode 100644 index 000000000..c1ffc76f9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/portfall.md @@ -0,0 +1,8 @@ +--- +title: Portfall +--- + +![Portfall](../../../../assets/showcase-images/portfall.webp) + +[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s +port-forwarding portal for easy access to all your cluster UIs diff --git a/docs/src/content/docs/community/showcase/resizem.md b/docs/src/content/docs/community/showcase/resizem.md new file mode 100644 index 000000000..3e8761dbc --- /dev/null +++ b/docs/src/content/docs/community/showcase/resizem.md @@ -0,0 +1,9 @@ +--- +title: Resizem +--- + +![Resizem Screenshot](../../../../assets/showcase-images/resizem.webp) + +[Resizem](https://github.com/barats/resizem) - is an app designed for bulk image +process. It is particularly useful for users who need to resize, convert, and +manage large numbers of image files at once. diff --git a/docs/src/content/docs/community/showcase/restic-browser.md b/docs/src/content/docs/community/showcase/restic-browser.md new file mode 100644 index 000000000..5a68cb02e --- /dev/null +++ b/docs/src/content/docs/community/showcase/restic-browser.md @@ -0,0 +1,9 @@ +--- +title: Restic Browser +--- + +![Restic Browser](../../../../assets/showcase-images/restic-browser-2.png) + +[Restic-Browser](https://github.com/emuell/restic-browser) - A simple, +cross-platform [restic](https://github.com/restic/restic) backup GUI for +browsing and restoring restic repositories. diff --git a/docs/src/content/docs/community/showcase/riftshare.md b/docs/src/content/docs/community/showcase/riftshare.md new file mode 100644 index 000000000..b6ed8f464 --- /dev/null +++ b/docs/src/content/docs/community/showcase/riftshare.md @@ -0,0 +1,23 @@ +--- +title: RiftShare +--- + +![RiftShare](../../../../assets/showcase-images/riftshare-main.webp) + +Easy, Secure, and Free file sharing for everyone. Learn more at +[Riftshare.app](https://riftshare.app) + +## Features + +- Easy secure file sharing between computers both in the local network and + through the internet +- Supports sending files or directories securely through the + [magic wormhole protocol](https://magic-wormhole.readthedocs.io/en/latest/) +- Compatible with all other apps using magic wormhole (magic-wormhole or + wormhole-william CLI, wormhole-gui, etc.) +- Automatic zipping of multiple selected files to send at once +- Full animations, progress bar, and cancellation support for sending and + receiving +- Native OS File Selection +- Open files in one click once received +- Auto Update - don't worry about having the latest release! diff --git a/docs/src/content/docs/community/showcase/scriptbar.md b/docs/src/content/docs/community/showcase/scriptbar.md new file mode 100644 index 000000000..1e03ab528 --- /dev/null +++ b/docs/src/content/docs/community/showcase/scriptbar.md @@ -0,0 +1,14 @@ +--- +title: ScriptBar +--- + +![ScriptBar](../../../../assets/showcase-images/scriptbar.webp) + +[ScriptBar](https://GitHub.com/raguay/ScriptBarApp) is a program to show the +output of scripts or [Node-Red](https://nodered.org) server. It runs scripts +defined in EmailIt program and shows the output. Scripts from xBar or TextBar +can be used, but currently on the TextBar scripts work well. It also displays +the output of scripts on your system. ScriptBar doesn't put them in the menubar, +but has them all in a convient window for easy viewing. You can have multiple +tabs to have many different things show. You can also keep the links to your +most visited web sites. diff --git a/docs/src/content/docs/community/showcase/snippetexpander.md b/docs/src/content/docs/community/showcase/snippetexpander.md new file mode 100644 index 000000000..0b5a88801 --- /dev/null +++ b/docs/src/content/docs/community/showcase/snippetexpander.md @@ -0,0 +1,34 @@ +--- +title: Snippet Expander +--- + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-select-snippet.png) + +Screenshot of Snippet Expander's Select Snippet window + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-add-snippet.png) + +Screenshot of Snippet Expander's Add Snippet screen + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png) + +Screenshot of Snippet Expander's Search & Paste window + +[Snippet Expander](https://snippetexpander.org) is "Your little expandable text +snippets helper", for Linux. + +Snippet Expander comprises of a GUI application built with Wails for managing +snippets and settings, with a Search & Paste window mode for quickly selecting +and pasting a snippet. + +The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all +communicate with a go-lang daemon via D-Bus. The daemon does the majority of the +work, managing the database of snippets and common settings, and providing +services for expanding and pasting snippets etc. + +Check out the +[source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38) +to see how the Wails app sends messages from the UI to the backend that are then +sent to the daemon, and subscribes to a D-Bus event to monitor changes to +snippets via another instance of the app or CLI and show them instantly in the +UI via a Wails event. diff --git a/docs/src/content/docs/community/showcase/surge.md b/docs/src/content/docs/community/showcase/surge.md new file mode 100644 index 000000000..3ab4444dd --- /dev/null +++ b/docs/src/content/docs/community/showcase/surge.md @@ -0,0 +1,9 @@ +--- +title: Surge +--- + +![Surge Screenshot](../../../../assets/showcase-images/surge.png) + +[Surge](https://getsurge.io/) is a p2p filesharing app designed to utilize +blockchain technologies to enable 100% anonymous file transfers. Surge is +end-to-end encrypted, decentralized and open source. diff --git a/docs/src/content/docs/community/showcase/tinyrdm.md b/docs/src/content/docs/community/showcase/tinyrdm.md new file mode 100644 index 000000000..f9551e046 --- /dev/null +++ b/docs/src/content/docs/community/showcase/tinyrdm.md @@ -0,0 +1,12 @@ +--- +title: Tiny RDM +--- + +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm1.webp) +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm2.webp) + +The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source, +modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database +management, and compatible with Windows, Mac, and Linux. It provides visual +key-value data operations, supports various data decoding and viewing options, +built-in console for executing commands, slow log queries and more. diff --git a/docs/src/content/docs/community/showcase/wailsterm.md b/docs/src/content/docs/community/showcase/wailsterm.md new file mode 100644 index 000000000..0521aac70 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wailsterm.md @@ -0,0 +1,8 @@ +--- +title: WailsTerm +--- + +![WailsTerm Screenshot](../../../../assets/showcase-images/wailsterm.webp) + +[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent +terminal app powered by Wails and Xterm.js. diff --git a/docs/src/content/docs/community/showcase/wally.md b/docs/src/content/docs/community/showcase/wally.md new file mode 100644 index 000000000..3f4ddbfa2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wally.md @@ -0,0 +1,10 @@ +--- +title: Wally +--- + +![Wally Screenshot](../../../../assets/showcase-images/wally.webp) + +[Wally](https://ergodox-ez.com/pages/wally) is the official firmware flasher for +[Ergodox](https://ergodox-ez.com/) keyboards. It looks great and is a fantastic +example of what you can achieve with Wails: the ability to combine the power of +Go and the rich graphical tools of the web development world. diff --git a/docs/src/content/docs/community/showcase/warmine.md b/docs/src/content/docs/community/showcase/warmine.md new file mode 100644 index 000000000..19de97f2a --- /dev/null +++ b/docs/src/content/docs/community/showcase/warmine.md @@ -0,0 +1,17 @@ +--- +title: Minecraft launcher for WarMine +--- + +![WarMine Screenshot](../../../../assets/showcase-images/warmine1.png) +![WarMine Screenshot](../../../../assets/showcase-images/warmine2.png) + +[Minecraft launcher for WarMine](https://warmine.ru/) is a Wails application, +that allows you to easily join modded game servers and manage your game +accounts. + +The Launcher downloads the game files, checks their integrity and launches the +game with a wide range of customization options for the launch arguments from +the backend. + +Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows +7-11. diff --git a/docs/src/content/docs/community/showcase/wombat.md b/docs/src/content/docs/community/showcase/wombat.md new file mode 100644 index 000000000..d61bdd6c9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wombat.md @@ -0,0 +1,7 @@ +--- +title: Wombat +--- + +![Wombat Screenshot](../../../../assets/showcase-images/wombat.webp) + +[Wombat](https://github.com/rogchap/wombat) is a cross platform gRPC client. diff --git a/docs/src/content/docs/community/showcase/ytd.md b/docs/src/content/docs/community/showcase/ytd.md new file mode 100644 index 000000000..bbcd6979f --- /dev/null +++ b/docs/src/content/docs/community/showcase/ytd.md @@ -0,0 +1,10 @@ +--- +title: Ytd +--- + +![Ytd Screenshot](../../../../assets/showcase-images/ytd.webp) + +[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) is an app for +downloading tracks from youtube, creating offline playlists and share them with +your friends, your friends will be able to playback your playlists or download +them for offline listening, has an built-in player. diff --git a/docs/src/content/docs/community/templates.md b/docs/src/content/docs/community/templates.md new file mode 100644 index 000000000..855b70c23 --- /dev/null +++ b/docs/src/content/docs/community/templates.md @@ -0,0 +1,132 @@ +--- +title: Templates +--- + +:::caution + +This page might be outdated for Wails v3. + +::: + + + +This page serves as a list for community supported templates. To build your own +template, please see the [Templates](https://wails.io/docs/guides/templates) +guide. + +:::tip[How to Submit a Template] + +You can click `Edit this page` at the bottom to include your templates. + +::: + +To use these templates, run +`wails init -n "Your Project Name" -t [the link below[@version]]` + +If there is no version suffix, the main branch code template is used by default. +If there is a version suffix, the code template corresponding to the tag of this +version is used. + +Example: +`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue` + +:::danger[Attention] + +**The Wails project does not maintain, is not responsible nor liable for 3rd +party templates!** + +If you are unsure about a template, inspect `package.json` and `wails.json` for +what scripts are run and what packages are installed. + +::: + +## Vue + +- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Wails + template based on Vue ecology (Integrated TypeScript, Dark theme, + Internationalization, Single page routing, TailwindCSS) +- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) - + A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier) +- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) - + A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier, Composition API with <script setup>) +- [wails-template-naive](https://github.com/tk103331/wails-template-naive) - + Wails template based on Naive UI (A Vue 3 Component Library) +- [wails-template-nuxt](https://github.com/gornius/wails-template-nuxt) - Wails + template using clean Nuxt3 and TypeScript with auto-imports for wails js + runtime +- [Wails-Tool-Template](https://github.com/xisuo67/Wails-Tool-Template) - Wails + template using Vue+TypeScript+Vite+Element-plus(ไปฟ็ฝ‘ๆ˜“ไบ‘) + +## Angular + +- [wails-template-angular](https://github.com/mateothegreat/wails-template-angular) - + Angular 15+ action packed & ready to roll to production. +- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) - + Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n + +## React + +- [wails-react-template](https://github.com/AlienRecall/wails-react-template) - + A template using reactjs +- [wails-react-template](https://github.com/flin7/wails-react-template) - A + minimal template for React that supports live development +- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A + template using Next.js and TypeScript +- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router +- [wails-template-nextjs-app-router-src](https://github.com/edai-git/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router src + example +- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - + A template for React + TypeScript + Vite + TailwindCSS +- [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) - + A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui + +## Svelte + +- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) - + A template using Svelte +- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) - + A template using Svelte and Vite +- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) - + A template using Svelte and Vite with TailwindCSS v3 +- [wails-svelte-tailwind-vite-template](https://github.com/PylotLight/wails-vite-svelte-tailwind-template/tree/master) - + An updated template using Svelte v4.2.0 and Vite with TailwindCSS v3.3.3 +- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) - + A template using SvelteKit +- [wails-template-shadcn-svelte](https://github.com/xijaja/wails-template-shadcn-svelte) - + A template using Sveltekit and Shadcn-Svelte + +## Solid + +- [wails-template-vite-solid-ts](https://github.com/xijaja/wails-template-solid-ts) - + A template using Solid + Ts + Vite +- [wails-template-vite-solid-js](https://github.com/xijaja/wails-template-solid-js) - + A template using Solid + Js + Vite + +## Elm + +- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) - + Develop your GUI app with functional programming and a **snappy** hot-reload + setup :tada: :rocket: +- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) - + Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading + supported. + +## HTMX + +- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) - + Use a unique combination of pure htmx for interactivity plus templ for + creating components and forms + +## Pure JavaScript (Vanilla) + +- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A + template with nothing but just basic JavaScript, HTML, and CSS + +## Lit (web components) + +- [wails-lit-shoelace-esbuild-template](https://github.com/Braincompiler/wails-lit-shoelace-esbuild-template) - + Wails template providing frontend with lit, Shoelace component library + + pre-configured prettier and typescript. diff --git a/docs/src/content/docs/contributing/_category_.json b/docs/src/content/docs/contributing/_category_.json new file mode 100644 index 000000000..5f76b677d --- /dev/null +++ b/docs/src/content/docs/contributing/_category_.json @@ -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 +} diff --git a/docs/src/content/docs/contributing/architecture.mdx b/docs/src/content/docs/contributing/architecture.mdx new file mode 100644 index 000000000..6490a35bc --- /dev/null +++ b/docs/src/content/docs/contributing/architecture.mdx @@ -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 + + +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 + + +--- + +## 2 ยท Runtime Call Flow + + +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") + + +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 + + +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 + + +* 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 + + +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" + + +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. diff --git a/docs/src/content/docs/contributing/asset-server.mdx b/docs/src/content/docs/contributing/asset-server.mdx new file mode 100644 index 000000000..e49cd6700 --- /dev/null +++ b/docs/src/content/docs/contributing/asset-server.mdx @@ -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:` and + tells the Go runtime to load `http://:` 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. diff --git a/docs/src/content/docs/contributing/binding-system.mdx b/docs/src/content/docs/contributing/binding-system.mdx new file mode 100644 index 000000000..071dc82f1 --- /dev/null +++ b/docs/src/content/docs/contributing/binding-system.mdx @@ -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)
โ€ข `frontend/src/wailsjs/**.ts` (TS defs)
โ€ข `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 +`.()` 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; + } +} +``` + +* 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:, 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! diff --git a/docs/src/content/docs/contributing/build-packaging.mdx b/docs/src/content/docs/contributing/build-packaging.mdx new file mode 100644 index 000000000..78878200e --- /dev/null +++ b/docs/src/content/docs/contributing/build-packaging.mdx @@ -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/.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! diff --git a/docs/src/content/docs/contributing/codebase-layout.mdx b/docs/src/content/docs/contributing/codebase-layout.mdx new file mode 100644 index 000000000..74cfc7e92 --- /dev/null +++ b/docs/src/content/docs/contributing/codebase-layout.mdx @@ -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:
โ€ข Dev: serves from disk & proxies Vite
โ€ข 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! diff --git a/docs/src/content/docs/contributing/extending-wails.mdx b/docs/src/content/docs/contributing/extending-wails.mdx new file mode 100644 index 000000000..ffff66b77 --- /dev/null +++ b/docs/src/content/docs/contributing/extending-wails.mdx @@ -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 `) +* 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//`) + * 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! diff --git a/docs/src/content/docs/contributing/index.mdx b/docs/src/content/docs/contributing/index.mdx new file mode 100644 index 000000000..a6dc96497 --- /dev/null +++ b/docs/src/content/docs/contributing/index.mdx @@ -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 + + + + The heart of every Wails app is Go code compiled into a native executable. + It owns application logic, system integration and performance-critical + operations. + + + + UI is written with standard web tech (React, Vue, Svelte, Vanilla, โ€ฆ) + rendered by a lightweight system WebView (WebKit on Linux/macOS, WebView2 on + Windows). + + + + A zero-copy, in-memory bridge enables **Goโ‡„JavaScript** calls with automatic + type conversion, event propagation and error forwarding. + + + + `wails3` orchestrates project creation, live-reload dev server, asset + bundling, cross-compilation and packaging (deb, rpm, AppImage, msi, dmgโ€ฆ). + + + +--- + +## Architectural Overview + + +```mermaid +flowchart TD + subgraph Developer Environment + CLI[wails3 CLI
Init ยท Dev ยท Build ยท Package] + end + + subgraph Build-Time + GEN[Binding System
(Static Analysis & Codegen)] + ASSET[Asset Server
(Dev Proxy ยท Embed FS)] + PKG[Build & Packaging
Pipeline] + end + + subgraph Runtime + RUNTIME[Desktop Runtime
(Window ยท Events ยท Dialogs)] + BIND[Bridge
(Message Processor)] + end + + subgraph Application + GO[Go Backend
(App Logic)] + WEB[Web Frontend
(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 +``` +
+ +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 `. | +| **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! diff --git a/docs/src/content/docs/contributing/runtime-internals.mdx b/docs/src/content/docs/contributing/runtime-internals.mdx new file mode 100644 index 000000000..aa4ae7ceb --- /dev/null +++ b/docs/src/content/docs/contributing/runtime-internals.mdx @@ -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.
2. Boots the **runtime** (`internal/runtime`).
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! diff --git a/docs/src/content/docs/contributing/template-system.mdx b/docs/src/content/docs/contributing/template-system.mdx new file mode 100644 index 000000000..ee03824d0 --- /dev/null +++ b/docs/src/content/docs/contributing/template-system.mdx @@ -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//`. +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//**` | 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 ` 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! diff --git a/docs/src/content/docs/contributing/testing-ci.mdx b/docs/src/content/docs/contributing/testing-ci.mdx new file mode 100644 index 000000000..7393e818e --- /dev/null +++ b/docs/src/content/docs/contributing/testing-ci.mdx @@ -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! diff --git a/docs/src/content/docs/credits.mdx b/docs/src/content/docs/credits.mdx new file mode 100644 index 000000000..78e9f5b7d --- /dev/null +++ b/docs/src/content/docs/credits.mdx @@ -0,0 +1,62 @@ +--- +title: Credits +--- + +{/* Import the auto-generated contributors file */} + +import Contributors from "../../assets/contributors.html"; + +- [Lea Anthony](https://github.com/leaanthony) - Project owner, lead developer +- [Stffabi](https://github.com/stffabi) - Technical lead, developer and + maintainer +- [Travis McLane](https://github.com/tmclane) - Cross-compilation work, MacOS + testing +- [Atterpac](https://github.com/atterpac) - Developer, support guru, powerhouse +- [Simon Thomas](mailto:enquiries@wails.io) - Growth Hacker +- [Lyimmi](https://github.com/Lyimmi) - All things Linux +- [fbbdev](https://github.com/fbbdev) - Bindings Generator guru & core contributor + +## Sponsors +
+ + ZSA + +Sponsors +Special thanks: +JetBrains + +## Contributors + + + +## Special Mentions + +- [John Chadwick](https://github.com/jchv) - His amazing work on + [go-webview2](https://github.com/jchv/go-webview2) and + [go-winloader](https://github.com/jchv/go-winloader) have made the Windows + version possible. +- [Tad Vizbaras](https://github.com/tadvi) - His winc project was the first step + down the path to a pure Go Wails. +- [Mat Ryer](https://github.com/matryer) - For advice, support and bants. +- [Byron Chris](https://github.com/bh90210) - For his long term contributions to + this project. +- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - His support and + feedback has been invaluable. +- [Justen Walker](https://github.com/justenwalker/) - For helping wrangle COM + issues which got v2 over the line. +- [Wang, Chi](https://github.com/patr0nus/) - The DeskGap project was a huge + influence on the direction of Wails v2. +- [Serge Zaitsev](https://github.com/zserge) - Whilst Wails does not use the + Webview project, it is still a source of inspiration. diff --git a/docs/src/content/docs/feedback.mdx b/docs/src/content/docs/feedback.mdx new file mode 100644 index 000000000..afe4a8bfb --- /dev/null +++ b/docs/src/content/docs/feedback.mdx @@ -0,0 +1,86 @@ +--- +title: v3 Alpha Feedback +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +We welcome (and encourage) your feedback! Please search for existing tickets or +posts before creating new ones. Here are the different ways to provide feedback: + + + + + If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord. + + - The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post. + - The post should be given the `Bug` tag. + - Please include the output of `wails doctor` in your post. + - If the bug is behavior that does not align with current documentation, e.g. a window does not resize properly, please do the following: + - Update an existing example in the `v3/example` directory or create a new example in the `v3/examples` folder that clearly shows the issue. + - Open a [PR](https://github.com/wailsapp/wails/pulls) with the title `[v3 alpha test] `. + - Please include a link to the PR in your post. + + :::caution + + _Remember_, unexpected behavior isn't necessarily a bug - it might just not do + what you expect it to do. Use `Suggestions` for this. + + ::: + + + + + + If you have a fix for a bug or an update for documentation, please do the following: + + - Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`. + - Create a post in the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel. + - The post should be given the `PR` tag. + - Please include a link to the PR in your post. + + + + + + If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord: + + - The post should be given the `Suggestion` tag. + + Please feel free to reach out to us on [Discord](https://discord.gg/bdj28QNHmT) if you have any questions. + + + + + + - Posts can be "upvoted" by using the :thumbsup: emoji. Please apply to any posts that are a priority for you. + - Please *don't* just add comments like "+1" or "me too". + - Please feel free to comment if there is more to add to the post, such as "this bug also affect ARM builds" or "Another option would be to ....." + + + + + +There is a list of known issues & work in progress can be found +[here](https://github.com/orgs/wailsapp/projects/6). + +## Things we are looking for feedback on + +- The API + - Is it easy to use? + - Does it do what you expect? + - Is it missing anything? + - Is there anything that should be removed? + - Is it consistent between Go and JS? +- The build system + - Is it easy to use? + - Can we improve it? +- The examples + - Are they clear? + - Do they cover the basics? +- Features + - What features are missing? + - What features are not needed? +- Documentation + - What could be clearer? diff --git a/docs/src/content/docs/getting-started/installation.mdx b/docs/src/content/docs/getting-started/installation.mdx new file mode 100644 index 000000000..2eb4e71a4 --- /dev/null +++ b/docs/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,114 @@ +--- +title: Installation +sidebar: + order: 10 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Supported Platforms + +- Windows 10/11 AMD64/ARM64 +- macOS 10.15+ AMD64 (Can deploy to macOS 10.13+) +- macOS 11.0+ ARM64 +- Ubuntu 24.04 AMD64/ARM64 (other Linux may work too!) + +## Dependencies + +Wails has a number of common dependencies that are required before installation: + + + + + Download Go from the [Go Downloads Page](https://go.dev/dl/). + + Ensure that you follow the official [Go installation instructions](https://go.dev/doc/install). You will also need to ensure that your `PATH` environment variable also includes the path to your `~/go/bin` directory. Restart your terminal and do the following checks: + + - Check Go is installed correctly: `go version` + - Check `~/go/bin` is in your PATH variable + - Mac / Linux: `echo $PATH | grep go/bin` + - Windows: `$env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' }` + + + + + + Although Wails doesn't require npm to be installed, it is needed by most of the bundled templates. + + Download the latest node installer from the [Node Downloads Page](https://nodejs.org/en/download/). It is best to use the latest release as that is what we generally test against. + + Run `npm --version` to verify. + + :::note + If you prefer a different package manager to npm, feel free to use it. You will need to update the project Taskfiles to use it. + ::: + + + + + +## Platform Specific Dependencies + +You will also need to install platform specific dependencies: + + + + + Wails requires that the xcode command line tools are installed. This can be + done by running: + + ```sh + xcode-select --install + ``` + + + + + Wails requires that the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is installed. Almost all Windows installations will already have this installed. You can check using the `wails doctor` command. + + + + + Linux requires the standard `gcc` build tools plus `gtk3` and `webkit2gtk`. Run wails doctor after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please let us know on discord. + + + + + +## Installation + +To install the Wails CLI using Go Modules, run the following commands: + +```shell +go install -v github.com/wailsapp/wails/v3/cmd/wails3@latest +``` + +If you would like to install the latest development version, run the following +commands: + +```shell +git clone https://github.com/wailsapp/wails.git +cd wails +git checkout v3-alpha +cd v3/cmd/wails3 +go install +``` + +When using the development version, all generated projects will use Go's +[replace](https://go.dev/ref/mod#go-mod-file-replace) directive to ensure +projects use the development version of Wails. + +## System Check + +Running `wails3 doctor` will check if you have the correct dependencies +installed. If not, it will advise on what is missing and help on how to rectify +any problems. + +## The `wails3` command appears to be missing? + +If your system is reporting that the `wails3` command is missing, check the +following: + +- Make sure you have followed the above `Go installation guide` correctly and + that the `go/bin` directory is in the `PATH` environment variable. +- Close/Reopen current terminals to pick up the new `PATH` variable. diff --git a/docs/src/content/docs/getting-started/your-first-app.mdx b/docs/src/content/docs/getting-started/your-first-app.mdx new file mode 100644 index 000000000..fa0978683 --- /dev/null +++ b/docs/src/content/docs/getting-started/your-first-app.mdx @@ -0,0 +1,275 @@ +--- +title: Your First Application +sidebar: + order: 20 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; +import { Badge } from '@astrojs/starlight/components'; +import { Steps } from "@astrojs/starlight/components"; +import { FileTree } from '@astrojs/starlight/components'; + +import wails_init from '../../../assets/wails_init.mp4'; +import wails_build from '../../../assets/wails_build.mp4'; +import wails_dev from '../../../assets/wails_dev.mp4'; + + +Creating your first application with Wails v3 is an exciting journey into +the world of modern desktop app development. This guide will walk you through +the process of creating a basic application, showcasing the power and simplicity +of Wails. + +
+
+ + + +1. ## Creating a New Project + + Open your terminal and run the following command to create a new Wails + project: + + ```bash + wails3 init -n myfirstapp + ``` + + This command creates a new directory called `myfirstapp` with all the + necessary files. + + + +2. ## Exploring the Project Structure + + Navigate to the `myfirstapp` directory. You'll find several files and + folders: + + + - build/ Contains files used by the build process + - appicon.png The application icon + - config.yml Build configuration + - Taskfile.yml Build tasks + - darwin/ macOS specific build files + - Info.dev.plist Development configuration + - Info.plist Production configuration + - Taskfile.yml macOS build tasks + - icons.icns macOS application icon + - linux/ Linux specific build files + - Taskfile.yml Linux build tasks + - appimage/ AppImage packaging + - build.sh AppImage build script + - nfpm/ NFPM packaging + - nfpm.yaml Package configuration + - scripts/ Build scripts + - windows/ Windows specific build files + - Taskfile.yml Windows build tasks + - icon.ico Windows application icon + - info.json Application metadata + - wails.exe.manifest Windows manifest file + - nsis/ NSIS installer files + - project.nsi NSIS project file + - wails_tools.nsh NSIS helper scripts + - frontend/ Frontend application files + - index.html Main HTML file + - main.js Main JavaScript file + - package.json NPM package configuration + - public/ Static assets + - Inter Font License.txt Font license + - .gitignore Git ignore file + - README.md Project documentation + - Taskfile.yml Project tasks + - go.mod Go module file + - go.sum Go module checksums + - greetservice.go Greeting service + - main.go Main application code + + + Take a moment to explore these files and familiarize yourself with the + structure. + + :::note + Although Wails v3 uses [Task](https://taskfile.dev/) as its + default build system, there is nothing stopping you from using `make` or any other + alternative build system. + ::: + +3. ## Building Your Application + + To build your application, execute: + + ```bash + wails3 build + ``` + + This command compiles a debug version of your application and saves it in a + new `bin` directory. + + :::note + `wails3 build` is shorthand for `wails3 task build` and will run the `build` task in `Taskfile.yml`. + ::: + + + + + Once built, you can run this like you would any normal application: + + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + ```sh + bin\myfirstapp.exe + ``` + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + You'll see a simple UI, the starting point for your application. As it is + the debug version, you'll also see logs in the console window. This is + useful for debugging purposes. + +4. ## Dev Mode + + We can also run the application in development mode. This mode allows you to + make changes to your frontend code and see the changes reflected in the + running application without having to rebuild the entire application. + + 1. Open a new terminal window. + 2. Run `wails3 dev`. The application will compile and run in debug mode. + 3. Open `frontend/index.html` in your editor of choice. + 4. Edit the code and change `Please enter your name below` to + `Please enter your name below!!!`. + 5. Save the file. + + This change will reflect in your application immediately. + + Any changes to backend code will trigger a rebuild: + + 1. Open `greetservice.go`. + 2. Change the line that has `return "Hello " + name + "!"` to + `return "Hello there " + name + "!"`. + 3. Save the file. + + The application will update within a matter of seconds. + + + +5. ## Packaging Your Application + + Once your application is ready for distribution, you can create + platform-specific packages: + + + + + + To create a `.app` bundle: + + ```bash + wails3 package + ``` + + This will create a production build and package it into a `.app` bundle in the `bin` directory. + + + + + To create an NSIS installer: + + ```bash + wails3 package + ``` + + This will create a production build and package it into an NSIS installer in the `bin` directory. + + + + + Wails supports multiple package formats for Linux distribution: + + ```bash + # Create all package types (AppImage, deb, rpm, and Arch Linux) + wails3 package + + # Or create specific package types + wails3 task linux:create:appimage # AppImage format + wails3 task linux:create:deb # Debian package + wails3 task linux:create:rpm # Red Hat package + wails3 task linux:create:aur # Arch Linux package + ``` + + + + + + For more detailed information about packaging options and configuration, + check out our [Packaging Guide](/guides/packaging). + +6. ## Setting up Version Control and Module Name + + Your project is created with the placeholder module name `changeme`. It's recommended to update this to match your repository URL: + + 1. Create a new repository on GitHub (or your preferred Git host) + 2. Initialize git in your project directory: + ```bash + git init + git add . + git commit -m "Initial commit" + ``` + 3. Set your remote repository (replace with your repository URL): + ```bash + git remote add origin https://github.com/username/myfirstapp.git + ``` + 4. Update your module name in `go.mod` to match your repository URL: + ```bash + go mod edit -module github.com/username/myfirstapp + ``` + 5. Push your code: + ```bash + git push -u origin main + ``` + + This ensures your Go module name aligns with Go's module naming conventions and makes it easier to share your code. + + :::tip[Pro Tip] + You can automate all of the initialisation steps by using the `-git` flag when creating your project: + ```bash + wails3 init -n myfirstapp -git github.com/username/myfirstapp + ``` + This supports various Git URL formats: + - HTTPS: `https://github.com/username/project` + - SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` + - Git protocol: `git://github.com/username/project` + - Filesystem: `file:///path/to/project.git` + ::: + + + +## Congratulations! + +You've just created, developed and packaged your first Wails application. +This is just the beginning of what you can achieve with Wails v3. + +## Next Steps + +If you are new to Wails, we recommend reading through our Tutorials next which will be a practical guide through +the various features of Wails. The first tutorial is [Creating a Service](/tutorials/01-creating-a-service). + +If you are a more advanced user, check out our [Guides](/guides) for more detailed information on how to use Wails. diff --git a/docs/src/content/docs/guides/_category_.json b/docs/src/content/docs/guides/_category_.json new file mode 100644 index 000000000..edc4bf6e3 --- /dev/null +++ b/docs/src/content/docs/guides/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Guides", + "position": 3, + "link": { + "type": "generated-index", + "description": "Comprehensive guides for developing Wails applications" + } +} diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx new file mode 100644 index 000000000..97206ec14 --- /dev/null +++ b/docs/src/content/docs/guides/cli.mdx @@ -0,0 +1,463 @@ +--- +title: CLI Reference +description: Complete reference for the Wails CLI commands +sidebar: + order: 1 +--- + +The Wails CLI provides a comprehensive set of commands to help you develop, build, and maintain your Wails applications. + +## Core Commands + +Core commands are the primary commands used for project creation, development, and building. + +All CLI commands are of the following format: `wails3 `. + +### `init` +Initializes a new Wails project. + +```bash +wails3 init [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------------------|----------------------|--------------------------| +| `-p` | Package name | `main` | +| `-t` | Template name or URL | `vanilla` | +| `-n` | Project name | | +| `-d` | Project directory | `.` | +| `-q` | Suppress output | `false` | +| `-l` | List templates | `false` | +| `-git` | Git repository URL | | +| `-productname` | Product name | `My Product` | +| `-productdescription` | Product description | `My Product Description` | +| `-productversion` | Product version | `0.1.0` | +| `-productcompany` | Company name | `My Company` | +| `-productcopyright` | Copyright notice | ` now, My Company` | +| `-productcomments` | File comments | `This is a comment` | +| `-productidentifier` | Product identifier | | + +The `-git` flag accepts various Git URL formats: +- HTTPS: `https://github.com/username/project` +- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` +- Git protocol: `git://github.com/username/project` +- Filesystem: `file:///path/to/project.git` + +When provided, this flag will: +1. Initialize a git repository in the project directory +2. Set the specified URL as the remote origin +3. Update the module name in `go.mod` to match the repository URL +4. Add all files + +### `dev` +Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected +in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application +will automatically rebuild and relaunch. + +```bash +wails3 dev [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------|----------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-port` | Vite dev server port | `9245` | +| `-s` | Enable HTTPS | `false` | + + + :::note + This is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file. + ::: + + +### `build` +Builds a debug version of your application. It defaults to building for the current platform and architecture. + + +```bash +wails3 build +``` + + :::note + This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. You can customise the build process by editing the `Taskfile.yml` file. + ::: + +### `package` + +Creates platform-specific packages for distribution. + + +```bash +wails3 package +``` + +#### Package Types + +The following package types are available for each platform: + +| Platform | Package Type | +|----------|-------------------------------------------| +| Windows | `.exe` | +| macOS | `.app`, | +| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | + + + + :::note + This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. You can customise the packaging process by editing the `Taskfile.yml` file. + ::: + + +### `doctor` +Performs a system check and displays a status report. + +```bash +wails3 doctor +``` + +## Generate Commands + +Generate commands help create various project assets like bindings, icons, and build files. All generate commands use the base command: `wails3 generate `. + +### `generate bindings` +Generates bindings and models for your Go code. + +```bash +wails3 generate bindings [flags] [patterns...] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|---------------------| +| `-f` | Additional Go build flags | | +| `-d` | Output directory | `frontend/bindings` | +| `-models` | Models filename | `models` | +| `-index` | Index filename | `index` | +| `-ts` | Generate TypeScript | `false` | +| `-i` | Use TS interfaces | `false` | +| `-b` | Use bundled runtime | `false` | +| `-names` | Use names instead of IDs | `false` | +| `-noindex` | Skip index files | `false` | +| `-dry` | Dry run | `false` | +| `-silent` | Silent mode | `false` | +| `-v` | Debug output | `false` | +| `-clean` | Clean output directory | `false` | + +### `generate build-assets` +Generates build assets for your application. + +```bash +wails3 generate build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|--------------------| +| `-name` | Project name | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | `com.wails.[name]` | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +### `generate icons` +Generates application icons. + +```bash +wails3 generate icons [flags] +``` + +#### Flags +| 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. + +```bash +wails3 generate syso [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|----------------------------| +| `-manifest` | Path to manifest file | Required | +| `-icon` | Path to icon file | Required | +| `-info` | Path to version info file | | +| `-arch` | Target architecture | Current GOARCH | +| `-out` | Output filename | `rsrc_windows_[arch].syso` | + +### `generate .desktop` +Generates a Linux .desktop file. + +```bash +wails3 generate .desktop [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------------|---------------------------|------------------| +| `-name` | Application name | Required | +| `-exec` | Executable path | Required | +| `-icon` | Icon path | | +| `-categories` | Application categories | `Utility` | +| `-comment` | Application comment | | +| `-terminal` | Run in terminal | `false` | +| `-keywords` | Search keywords | | +| `-version` | Application version | | +| `-genericname` | Generic name | | +| `-startupnotify` | Show startup notification | `false` | +| `-mimetype` | Supported MIME types | | +| `-output` | Output filename | `[name].desktop` | + +### `generate runtime` +Generates the pre-built version of the runtime. + +```bash +wails3 generate runtime +``` + +### `generate constants` +Generates JavaScript constants from Go code. + +```bash +wails3 generate constants +``` + +### `generate appimage` +Generates a Linux AppImage. + +```bash +wails3 generate appimage [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|-----------------------|----------------| +| `-binary` | Path to binary | Required | +| `-icon` | Path to icon file | Required | +| `-desktop` | Path to .desktop file | Required | +| `-builddir` | Build directory | Temp directory | +| `-output` | Output directory | `.` | + +Base command: `wails3 service` + +## Service Commands + +Service commands help manage Wails services. All service commands use the base command: `wails3 service `. + +### `service init` +Initializes a new service. + +```bash +wails3 service init [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|---------------------|-------------------| +| `-n` | Service name | `example_service` | +| `-d` | Service description | `Example service` | +| `-p` | Package name | | +| `-o` | Output directory | `.` | +| `-q` | Suppress output | `false` | +| `-a` | Author name | | +| `-v` | Version | | +| `-w` | Website URL | | +| `-r` | Repository URL | | +| `-l` | License | | + +Base command: `wails3 tool` + +## Tool Commands + +Tool commands provide utilities for development and debugging. All tool commands use the base command: `wails3 tool `. + +### `tool checkport` +Checks if a port is open. Useful for testing if vite is running. + +```bash +wails3 tool checkport [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------|---------------|-------------| +| `-port` | Port to check | `9245` | +| `-host` | Host to check | `localhost` | + +### `tool watcher` +Watches files and runs a command when they change. + +```bash +wails3 tool watcher [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|---------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-ignore` | Patterns to ignore | | +| `-include` | Patterns to include | | + +### `tool cp` +Copies files. + +```bash +wails3 tool cp +``` + +### `tool buildinfo` +Shows build information about the application. + +```bash +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). + +```bash +wails3 tool package [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------|--------------------------------------|----------| +| `-format` | Package format (deb, rpm, archlinux) | `deb` | +| `-name` | Executable name | Required | +| `-config` | Config file path | Required | +| `-out` | Output directory | `.` | + +#### Flags +| Flag | Description | Default | +|-----------|--------------------------------------|---------| +| `-format` | Package format (deb, rpm, archlinux) | `deb` | +| `-name` | Executable name | `myapp` | +| `-config` | Config file path | | +| `-out` | Output directory | `.` | + +Base command: `wails3 update` + +## Update Commands + +Update commands help manage and update project assets. All update commands use the base command: `wails3 update `. + +### `update cli` +Updates the Wails CLI to a new version. + +```bash +wails3 update cli [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------------|---------| +| `-pre` | Update to latest pre-release | `false` | +| `-version` | Update to specific version | | +| `-nocolour` | Disable colored output | `false` | + +The update cli command allows you to update your Wails CLI installation. By default, it updates to the latest stable release. +You can use the `-pre` flag to update to the latest pre-release version, or specify a particular version using the `-version` flag. + +After updating, remember to update your project's go.mod file to use the same version: +```bash +require github.com/wailsapp/wails/v3 v3.x.x +``` + +### `update build-assets` +Updates the build assets using the given config file. + +```bash +wails3 update build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|---------| +| `-config` | Config file path | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +Base command: `wails3` + +## Utility Commands + +Utility commands provide helpful shortcuts for common tasks. Use these commands directly with the base command: `wails3 `. + +### `docs` +Opens the Wails documentation in your default browser. + +```bash +wails3 docs +``` + +### `releasenotes` +Shows the release notes for the current or specified version. + +```bash +wails3 releasenotes [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|-----------------------------------|---------| +| `-v` | Version to show release notes for | | +| `-n` | Disable colour output | `false` | + +### `version` +Prints the current version of Wails. + +```bash +wails3 version +``` + +### `sponsor` +Opens the Wails sponsorship page in your default browser. + +```bash +wails3 sponsor diff --git a/docs/src/content/docs/guides/custom-protocol-association.mdx b/docs/src/content/docs/guides/custom-protocol-association.mdx new file mode 100644 index 000000000..4d5bf1eca --- /dev/null +++ b/docs/src/content/docs/guides/custom-protocol-association.mdx @@ -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}}`. + + + +## 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) + } +} +``` + + + +## 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)" + CFBundleURLTypes + + + CFBundleURLName + My Application Custom Protocol + CFBundleURLSchemes + + myapp + + + + + ``` +- **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. + + + +### 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 `Test Link` and open it in a browser. + + + +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. diff --git a/docs/src/content/docs/guides/custom-templates.mdx b/docs/src/content/docs/guides/custom-templates.mdx new file mode 100644 index 000000000..3da561b35 --- /dev/null +++ b/docs/src/content/docs/guides/custom-templates.mdx @@ -0,0 +1,293 @@ +--- +title: Creating Custom Templates +description: Learn how to create and customise your own Wails v3 templates +sidebar: + order: 50 +--- + +This guide will walk you through the process of creating a custom template for Wails v3. + +## Why would I make a custom template? + +Wails comes with a number of pre-configured templates that allow you to get your application up and running quickly. But if you need a more customised setup, you can create your own template to suit your needs. This can then be shared with the Wails community for others to use. + +### 1. Generating a Template + +To create a custom template, you can use the `wails generate template` command: + +```bash +wails3 generate template -name mytemplate +``` + +This will create a new directory called "mytemplate" in your current directory. + +The `wails3 generate template` command supports the following options: + +| Option | Description | Default | +|----------------|---------------------------------------------------|-------------------| +| `-name` | The name of your template (required) | - | +| `-frontend` | Path to an existing frontend directory to include | - | +| `-author` | The author of the template | - | +| `-description` | A description of the template | - | +| `-helpurl` | URL for template documentation | - | +| `-dir` | Directory to generate the template in | Current directory | +| `-version` | Template version | v0.0.1 | + +For example, to create a template with all options: + +```bash +wails3 generate template \ + -name "My Custom Template" \ + -frontend ./my-existing-frontend \ + -author "Your Name" \ + -description "A template with my preferred setup" \ + -helpurl "https://github.com/yourusername/template-docs" \ + -dir ./templates \ + -version "v1.0.0" +``` + + :::tip + Using the `-frontend` option will copy an existing web frontend project into the template. + ::: + +### 2. Configure Template Metadata + +If you didn't specify the template configuration when generating the template, you can update the `template.json` file in the template directory: + +```json5 +{ + "name": "Your Template Name", // Display name of your template + "shortname": "template-shortname", // Used when referencing your template + "author": "Your Name", // Template author + "description": "Template description", // Template description + "helpurl": "https://your-docs.com", // Documentation URL + "version": "v0.0.1", // Template version + "schema": 3 // Must be kept as 3 for Wails v3 +} +``` +:::caution +The `schema` field must remain set to `3` for compatibility with Wails v3. +::: + +### 3. Set Up Build Tasks + +In the `build` directory is `Taskfile.yml` where you can define your template's build process. +This file uses [Task](https://taskfile.dev) for build automation. The key steps are: + +```yaml +tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + + +``` + +### 4. Frontend Setup + +If you did not use `-frontend` when generating the template, you need to add frontend files to your template. + +There are a number of ways to set up your frontend: starting from scratch or using an existing framework. + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + + If you want to start from scratch, you can create your frontend project just like you would for any web application. + The `frontend` directory in your template is just a regular directory where you can set up your preferred + development environment. You might want to use build tools like Vite, webpack, or even just plain HTML, CSS, and + JavaScript - it's entirely up to you! + + For example, if you're using Vite, you could navigate to the `frontend` directory and run: + + ```bash + npm create vite@latest . + ``` + + Then follow the prompts to set up your project exactly how you want it. The key thing to remember is that this is just a regular frontend project - you can use any tools, frameworks, or libraries you're familiar with. + + + For this example, we'll use [Vite](https://vitejs.dev/) to set up a React frontend project: + + ```bash + npm create vite@latest frontend -- --template react + cd frontend + npm install + ``` + + + + + +Now you have the frontend files in place, update `common/Taskfile.yml` with the appropriate commands: + ```yaml + tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + ``` + + :::note + For this example, the default Tasks do not need updating as they use the standard `npm install` and `npm run build` commands. + ::: + +### 5. Configure the Go Application + +The default files in the template directory are sufficient to get users started. However, you may want to provide some additional functionality to demonstrate your template's capabilities. The best way to do this is to rename `main.go.tmpl` to `main.go` and edit it like any other Go file. Once finished, ensure you rename it back to `main.go.tmpl` before committing your changes. If you do not care about having a templated `main.go` file (the default template injests the project name into the `Name` field of the application), you can skip this step. + +#### Template Variables + +Wails uses Go's templating engine to process files with the `.tmpl` extension. During template generation, several variables are available for use in your template files: + +| Variable | Description | Example | +|----------------------|----------------------------------|-----------------------------------| +| `Name` | The name of the project | `"MyApp"` | +| `BinaryName` | The name of the generated binary | `"myapp"` | +| `ProductName` | The product name | `"My Application"` | +| `ProductDescription` | Description of the product | `"An awesome application"` | +| `ProductVersion` | Version of the product | `"1.0.0"` | +| `ProductCompany` | Company name | `"My Company Ltd"` | +| `ProductCopyright` | Copyright information | `"Copyright 2024 My Company Ltd"` | +| `ProductComments` | Additional product comments | `"Built with Wails"` | +| `ProductIdentifier` | Unique product identifier | `"com.mycompany.myapp"` | +| `Typescript` | Whether TypeScript is being used | `true` or `false` | +| `WailsVersion` | The version of Wails being used | `"3.0.0"` | + +You can use these variables in your template files using Go's template syntax: + +```go +// main.go.tmpl +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "{{.ProductName}}", + Description: "{{.ProductDescription}}", + }) + // ... +} +``` + + :::tip + Templating can be applied to any file in your template, even html files, so long as the filename has `.tmpl` in its name. + ::: + +### 6. Testing Your Template + +To test your template: + +1. Generate a project using your template: `wails3 init -n testproject -t path/to/your/template` +2. Run `wails3 build` to generate the production build and make sure the binary in `bin` runs correctly +3. Run `wails3 dev` to start the development server. +4. Test that changes to the frontend code are reflected in the application. +5. Test that changes to the Go code rebuild and relaunch the application + +### 7. Sharing Your Template + +Once your template is ready, you can share it with the community by hosting it on GitHub. Here's how: + +1. Create a new GitHub repository for your template +2. Push your template code to the repository +3. Tag your releases using semantic versioning (e.g., v1.0.0) + +Users can then use your template directly from GitHub using the HTTPS URL: + +```bash +wails3 init -n myapp -t https://github.com/yourusername/your-template +``` + +You can also specify a particular version using the URL format: + +```bash +# Use a specific version tag +wails3 init -n myapp -t https://github.com/yourusername/your-template/releases/tag/v1.0.0 + +# Use a specific branch +wails3 init -n myapp -t https://github.com/yourusername/your-template/tree/main +``` + +To test your template before sharing: + +1. Push your changes to GitHub +2. Create a new test project using the HTTPS URL: + ```bash + wails3 init -n testapp -t https://github.com/yourusername/your-template + ``` +3. Verify that all files are correctly generated +4. Test the build and development workflow as described in the testing section + + :::note + Make sure your repository is public if you want others to use your template. + ::: + +For more information, visit the [Wails documentation](https://wails.io) + + +## Best Practices + +Let's talk about some key practices that will help make your template more useful and maintainable. Here are the main areas to focus on: + +1. **Make Your Template Easy to Understand** + - Write a clear, helpful README.md that gets users started quickly + - Add comments in your config files to explain the "why" behind your choices + - Show examples of common customisations - users love to see real-world use cases! + +2. **Keep Dependencies Happy** + - Stay on top of your frontend package updates + - Lock down specific versions in package.json to avoid surprises + - Let users know upfront what they'll need to have installed + +3. **Love Your Template** + - Keep it fresh with regular updates + - Give it a thorough test drive before sharing + - Share it with the Wails community - we'd love to see what you create! diff --git a/docs/src/content/docs/guides/customising-windows.mdx b/docs/src/content/docs/guides/customising-windows.mdx new file mode 100644 index 000000000..bded8bd8b --- /dev/null +++ b/docs/src/content/docs/guides/customising-windows.mdx @@ -0,0 +1,129 @@ +--- +title: Customising Windows in Wails +sidebar: + order: 10 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +Wails provides an API to control the appearance and functionality of the +controls of a window. This functionality is available on Windows and macOS, but +not on Linux. + +## Setting the Window Button States + +The button states are defined by the `ButtonState` enum: + +```go +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) +``` + +- `ButtonEnabled`: The button is enabled and visible. +- `ButtonDisabled`: The button is visible but disabled (grayed out). +- `ButtonHidden`: The button is hidden from the titlebar. + +The button states can be set during window creation or at runtime. + +### Setting Button States During Window Creation + +When creating a new window, you can set the initial state of the buttons using +the `WebviewWindowOptions` struct: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonHidden, + MaximiseButtonState: application.ButtonDisabled, + CloseButtonState: application.ButtonEnabled, + }) + + app.Run() +} +``` + +In the example above, the minimise button is hidden, the maximise button is +inactive (grayed out), and the close button is active. + +### Setting Button States at Runtime + +You can also change the button states at runtime using the following methods on +the `Window` interface: + +```go +window.SetMinimiseButtonState(wails.ButtonHidden) +window.SetMaximiseButtonState(wails.ButtonEnabled) +window.SetCloseButtonState(wails.ButtonDisabled) +``` + +### Platform Differences + +The button state functionality behaves slightly differently on Windows and +macOS: + +| | Windows | Mac | +|-----------------------|------------------------|------------------------| +| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close | +| Hide Min | Disables Min | Hides Min button | +| Hide Max | Disables Max | Hides Max button | +| Hide Close | Hides all controls | Hides Close | + +Note: On Windows, it is not possible to hide the Min/Max buttons individually. +However, disabling both will hide both of the controls and only show the close +button. + +### Controlling Window Style (Windows) + +To control the style of the titlebar on Windows, you can use the `ExStyle` field +in the `WebviewWindowOptions` struct: + +Example: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST, + }, + }) + + app.Run() +} +``` + +Other options that affect the Extended Style of a window will be overridden by +this setting: + +- HiddenOnTaskbar +- AlwaysOnTop +- IgnoreMouseEvents +- BackgroundType diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx new file mode 100644 index 000000000..c03f5408c --- /dev/null +++ b/docs/src/content/docs/guides/events-reference.mdx @@ -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. \ No newline at end of file diff --git a/docs/src/content/docs/guides/file-associations.mdx b/docs/src/content/docs/guides/file-associations.mdx new file mode 100644 index 000000000..eebc29364 --- /dev/null +++ b/docs/src/content/docs/guides/file-associations.mdx @@ -0,0 +1,171 @@ +--- +title: File Associations +sidebar: + order: 20 +--- + +import { Steps } from "@astrojs/starlight/components"; +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +File associations allow your application to handle specific file types when +users open them. This is particularly useful for text editors, image viewers, or +any application that works with specific file formats. This guide explains how +to implement file associations in your Wails v3 application. + +## Overview + +File association support in Wails v3 is currently available for: + +- Windows (NSIS installer packages) +- macOS (application bundles) + +## Configuration + +File associations are configured in the `config.yml` file located in your +project's `build` directory. + +### Basic Configuration + +To set up file associations: + +1. Open `build/config.yml` +2. Add your file associations under the `fileAssociations` section +3. Run `wails3 update build-assets` to update the build assets +4. Set the `FileAssociations` field in the application options +5. Package your application using `wails3 package` + +Here's an example configuration: + +```yaml +fileAssociations: + - ext: myapp + name: MyApp Document + description: MyApp Document File + iconName: myappFileIcon + role: Editor + - ext: custom + name: Custom Format + description: Custom File Format + iconName: customFileIcon + role: Editor +``` + +### Configuration Properties + +| Property | Description | Platform | +|-------------|------------------------------------------------------------------|----------| +| ext | File extension without the leading period (e.g., `txt`) | All | +| name | Display name for the file type | All | +| 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 + +To handle file open events in your application, you can listen for the +`events.Common.ApplicationOpenedWithFile` event: + +```go +func main() { + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + + // Listen for files being used to open the application + app.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(event *application.ApplicationEvent) { + associatedFile := event.Context().Filename() + application.InfoDialog().SetMessage("Application opened with file: " + associatedFile).Show() + }) + + // Create your window and run the app... +} + +``` + +## Step-by-Step Tutorial + +Let's walk through setting up file associations for a simple text editor: + + + +1. ### Create Icons + + - Create icons for your file type (recommended sizes: 16x16, 32x32, 48x48, + 256x256) + - Save the icons in your project's `build` folder + - Name them according to your `iconName` configuration (e.g., + `textFileIcon.png`) + + :::tip + You can use `wails3 generate icons` to generate the required icons for you. + 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: + + ```yaml + # build/config.yml + fileAssociations: + - ext: txt + name: Text Document + description: Plain Text Document + iconName: textFileIcon + role: Editor + ``` + +3. ### Update Build Assets + + Run the following command to update the build assets: + + ```bash + wails3 update build-assets + ``` + +4. ### Set File Associations in the Application Options + + In your `main.go` file, set the `FileAssociations` field in the application + options: + + ```go + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + ``` + + :::tip[Why are file extensions required in both the application config and config.yml?] + + On Windows, when a file is opened with a file association, the application is + launched with the filename as the first argument to the application. The + application has no way of knowing if the first argument is a file or a command + line argument, so it uses the `FileAssociations` field in the application + options to determine if the first argument is an associated file or not. + + ::: + +5. ### Package Your Application + + Package your application using the following command: + + ```bash + wails3 package + ``` + + The packaged application will be created in the `bin` directory. You can then + install and test the application. + + ## Additional Notes + + - Icons should be provided in PNG format in the build folder + - Testing file associations requires installing the packaged application + + \ No newline at end of file diff --git a/docs/src/content/docs/guides/gin-routing.mdx b/docs/src/content/docs/guides/gin-routing.mdx new file mode 100644 index 000000000..b9f59cb77 --- /dev/null +++ b/docs/src/content/docs/guides/gin-routing.mdx @@ -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 + + + + + + + +

+    
+    
+
+
+```
+
+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
+    
+```
+
+## 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).
diff --git a/docs/src/content/docs/guides/gin-services.mdx b/docs/src/content/docs/guides/gin-services.mdx
new file mode 100644
index 000000000..73c56afba
--- /dev/null
+++ b/docs/src/content/docs/guides/gin-services.mdx
@@ -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
+
+
+
+    
+    
+    Gin Service Example
+    
+
+
+    

Gin Service Example

+ +
+

API Endpoints

+

Try the Gin API endpoints mounted at /api:

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

Event Communication

+

Trigger an event to communicate with the Gin service:

+ + + +
+
Event responses will appear here...
+
+
+ + + + + + +``` + +## 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`. diff --git a/docs/src/content/docs/guides/menus.mdx b/docs/src/content/docs/guides/menus.mdx new file mode 100644 index 000000000..02ee9de30 --- /dev/null +++ b/docs/src/content/docs/guides/menus.mdx @@ -0,0 +1,531 @@ +--- +title: Menus +description: A guide to creating and customising menus in Wails v3 +--- + +Wails v3 provides a powerful menu system that allows you to create both application menus and context menus. This guide will walk you through the various features and capabilities of the menu system. + +### Creating a Menu + +To create a new menu, use the `New()` method from the Menus manager: + +```go +menu := app.Menus.New() +``` + +### Adding Menu Items + +Wails supports several types of menu items, each serving a specific purpose: + +#### Regular Menu Items +Regular menu items are the basic building blocks of menus. They display text and can trigger actions when clicked: + +```go +menuItem := menu.Add("Click Me") +``` + +#### Checkboxes +Checkbox menu items provide a toggleable state, useful for enabling/disabling features or settings: + +```go +checkbox := menu.AddCheckbox("My checkbox", true) // true = initially checked +``` + +#### Radio Groups +Radio groups allow users to select one option from a set of mutually exclusive choices. They are automatically created when radio items are placed next to each other: + +```go +menu.AddRadio("Option 1", true) // true = initially selected +menu.AddRadio("Option 2", false) +menu.AddRadio("Option 3", false) +``` + +#### Separators +Separators are horizontal lines that help organise menu items into logical groups: + +```go +menu.AddSeparator() +``` + +#### Submenus +Submenus are nested menus that appear when hovering over or clicking a menu item. They're useful for organizing complex menu structures: + +```go +submenu := menu.AddSubmenu("File") +submenu.Add("Open") +submenu.Add("Save") +``` + +#### Combining menus +A menu can be added into another menu by appending or prepending it. +```go +menu := app.Menus.New() +menu.Add("First Menu") + +secondaryMenu := app.Menus.New() +secondaryMenu.Add("Second Menu") + +// insert 'secondaryMenu' after 'menu' +menu.Append(secondaryMenu) + +// insert 'secondaryMenu' before 'menu' +menu.Prepend(secondaryMenu) + +// update the menu +menu.Update() +``` + +:::note +By default, `prepend` and `append` will share state with the original menu. If you want to create a new menu with its own state, +you can call `.Clone()` on the menu. + +E.g: `menu.Append(secondaryMenu.Clone())` +::: + +#### Clearing a menu +In some cases it'll be better to construct a whole new menu if you are working with a variable amount of menu items. + +This will clear all items on an existing menu and allows you to add items again. + +```go +menu := app.Menus.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be updated +menu.Clear() +menu.Add("Update complete!") +menu.Update() +``` + +:::note +Clearing a menu simply clears the menu items at the top level. Whilst Submenus won't be visible, they will still occupy memory +so be sure to manage your menus carefully. +::: + +#### Destroying a menu + +If you want to clear and release a menu, use the `Destroy()` method: + +```go +menu := app.Menus.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be destroyed +menu.Destroy() +``` + + +### Menu Item Properties + +Menu items have several properties that can be configured: + +| Property | Method | Description | +|-------------|--------------------------|-----------------------------------------------------| +| Label | `SetLabel(string)` | Sets the display text | +| Enabled | `SetEnabled(bool)` | Enables/disables the item | +| Checked | `SetChecked(bool)` | Sets the checked state (for checkboxes/radio items) | +| Tooltip | `SetTooltip(string)` | Sets the tooltip text | +| Hidden | `SetHidden(bool)` | Shows/hides the item | +| Accelerator | `SetAccelerator(string)` | Sets the keyboard shortcut | + +### Menu Item States + +Menu items can be in different states that control their visibility and interactivity: + +#### Visibility + +Menu items can be shown or hidden dynamically using the `SetHidden()` method: + +```go +menuItem := menu.Add("Dynamic Item") + +// Hide the menu item +menuItem.SetHidden(true) + +// Show the menu item +menuItem.SetHidden(false) + +// Check current visibility +isHidden := menuItem.Hidden() +``` + +Hidden menu items are completely removed from the menu until shown again. This is useful for contextual menu items that should only appear in certain application states. + +#### Enabled State + +Menu items can be enabled or disabled using the `SetEnabled()` method: + +```go +menuItem := menu.Add("Save") + +// Disable the menu item +menuItem.SetEnabled(false) // Item appears grayed out and cannot be clicked + +// Enable the menu item +menuItem.SetEnabled(true) // Item becomes clickable again + +// Check current enabled state +isEnabled := menuItem.Enabled() +``` + +Disabled menu items remain visible but appear grayed out and cannot be clicked. This is commonly used to indicate that an action is currently unavailable, such as: +- Disabling "Save" when there are no changes to save +- Disabling "Copy" when nothing is selected +- Disabling "Undo" when there's no action to undo + +#### Dynamic State Management + +You can combine these states with event handlers to create dynamic menus: + +```go +saveMenuItem := menu.Add("Save") + +// Initially disable the Save menu item +saveMenuItem.SetEnabled(false) + +// Enable Save only when there are unsaved changes +documentChanged := func() { + saveMenuItem.SetEnabled(true) + menu.Update() // Remember to update the menu after changing states +} + +// Disable Save after saving +documentSaved := func() { + saveMenuItem.SetEnabled(false) + menu.Update() +} +``` + +### Event Handling + +Menu items can respond to click events using the `OnClick` method: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Handle the click event + println("Menu item clicked!") +}) +``` + +The context provides information about the clicked menu item: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Get the clicked menu item + clickedItem := ctx.ClickedMenuItem() + // Get its current state + isChecked := clickedItem.Checked() +}) +``` + +### Role-Based Menu Items + +Wails provides a set of predefined menu roles that automatically create menu items with standard functionality. Here are the supported menu roles: + +#### Complete Menu Structures +These roles create entire menu structures with common functionality: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | macOS only | +| `EditMenu` | Standard Edit menu with Undo, Redo, Cut, Copy, Paste, etc. | All platforms | +| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms | +| `WindowMenu` | Window controls (Minimise, Zoom, etc.) | All platforms | +| `HelpMenu` | Help menu with "Learn More" link to Wails website | All platforms | + +#### Individual Menu Items +These roles can be used to add individual menu items: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `About` | Show application About dialog | All platforms | +| `Hide` | Hide application | macOS only | +| `HideOthers` | Hide other applications | macOS only | +| `UnHide` | Show hidden application | macOS only | +| `CloseWindow` | Close current window | All platforms | +| `Minimise` | Minimise window | All platforms | +| `Zoom` | Zoom window | macOS only | +| `Front` | Bring window to front | macOS only | +| `Quit` | Quit application | All platforms | +| `Undo` | Undo last action | All platforms | +| `Redo` | Redo last action | All platforms | +| `Cut` | Cut selection | All platforms | +| `Copy` | Copy selection | All platforms | +| `Paste` | Paste from clipboard | All platforms | +| `PasteAndMatchStyle` | Paste and match style | macOS only | +| `SelectAll` | Select all | All platforms | +| `Delete` | Delete selection | All platforms | +| `Reload` | Reload current page | All platforms | +| `ForceReload` | Force reload current page | All platforms | +| `ToggleFullscreen` | Toggle fullscreen mode | All platforms | +| `ResetZoom` | Reset zoom level | All platforms | +| `ZoomIn` | Increase zoom | All platforms | +| `ZoomOut` | Decrease zoom | All platforms | + +Here's an example showing how to use both complete menus and individual roles: + +```go +menu := app.Menus.New() + +// Add complete menu structures +menu.AddRole(application.AppMenu) // macOS only +menu.AddRole(application.EditMenu) // Common edit operations +menu.AddRole(application.ViewMenu) // View controls +menu.AddRole(application.WindowMenu) // Window controls + +// Add individual role-based items to a custom menu +fileMenu := menu.AddSubmenu("File") +fileMenu.AddRole(application.CloseWindow) +fileMenu.AddSeparator() +fileMenu.AddRole(application.Quit) +``` + +## Application Menus + +Application menus are the menus that appear at the top of your application window (Windows/Linux) or at the top of the screen (macOS). + + +### Application Menu Behaviour + +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{ + Title: "Custom Menu Window", + Windows: application.WindowsWindow{ + Menu: customMenu, // Override application menu for this window + }, +}) +``` + +Here's a complete example showing these different menu behaviours: + +```go +func main() { + app := application.New(application.Options{}) + + // Create application menu + 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.Windows.Current() + window.SetTitle("New Window") + }) + + // Set as application menu - this is for macOS + app.Menus.Set(appMenu) + + // Window with custom menu on Windows + customMenu := app.Menus.New() + customMenu.Add("Custom Action") + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Custom Menu", + Windows: application.WindowsWindow{ + Menu: customMenu, + }, + }) + + app.Run() +} +``` + +## Context Menus + +Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element. + +### Default Context Menu + +The default context menu is the webview's built-in context menu that provides system-level operations such as: +- Copy, Cut, and Paste for text manipulation +- Text selection controls +- Spell checking options + +#### Controlling the Default Context Menu + +You can control when the default context menu appears using the `--default-contextmenu` CSS property: + +```html + +
+ + +
+ + +
+
Custom context menu only
+
+ + +
+ +

Select this text to see the default menu

+ +
+``` + +:::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: + +1. Child elements inherit their parent's context menu setting unless explicitly overridden +2. The most specific (closest) setting takes precedence +3. The `auto` value can be used to reset to default behaviour + +Example of nested context menu behaviour: + +```html + +
+ +

No context menu here

+ + +
+

Context menu shown here

+ + + Also has context menu + + +
+

Shows menu only when text is selected

+
+
+
+``` + +### Custom Context Menus + +Custom context menus allow you to provide application-specific actions that are relevant to the element being clicked. They're particularly useful for: +- File operations in a document manager +- Image manipulation tools +- Custom actions in a data grid +- Component-specific operations + +#### Creating a Custom Context Menu + +When creating a custom context menu, you provide a unique identifier (name) that links the menu to HTML elements: + +```go +// Create a context menu with identifier "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: +1. Link HTML elements to this specific context menu +2. Identify which menu should be shown when right-clicking +3. Allow menu updates and cleanup + +#### Context Data + +When handling context menu events, you can access both the clicked menu item and its associated context data: + +```go +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + // Get the clicked menu item + menuItem := ctx.ClickedMenuItem() + + // Get the context data as a string + contextData := ctx.ContextMenuData() + + // Check if the menu item is checked (for checkbox/radio items) + isChecked := ctx.IsChecked() + + // Use the data + if contextData != "" { + processItem(contextData) + } +}) +``` + +The context data is passed from the HTML element's `--custom-contextmenu-data` property and is available in the click handler through `ctx.ContextMenuData()`. This is particularly useful when: + +- Working with lists or grids where each item needs unique identification +- Handling operations on specific components or elements +- Passing state or metadata from the frontend to the backend + +#### Context Menu Management + +After making changes to a context menu, call the `Update()` method to apply the changes: + +```go +contextMenu.Update() +``` + +When you no longer need a context menu, you can destroy it: + +```go +contextMenu.Destroy() +``` +:::danger[Warning] +After calling `Destroy()`, using the context menu reference again will result in a panic. +::: + +### Real-World Example: Image Gallery + +Here's a complete example of implementing a custom context menu for an image gallery: + +```go +// Backend: Create the context menu +imageMenu := application.NewContextMenu() +app.ContextMenus.Add("imageMenu", imageMenu) + +// Add relevant operations +imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) { + // Get the image ID from context data + if imageID := ctx.ContextMenuData(); imageID != "" { + openFullSizeImage(imageID) + } +}) + +imageMenu.Add("Download").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + downloadImage(imageID) + } +}) + +imageMenu.Add("Share").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + showShareDialog(imageID) + } +}) +``` + +```html + + +``` + +In this example: +1. The context menu is created with the identifier "imageMenu" +2. Each image container is linked to the menu using `--custom-contextmenu: imageMenu` +3. Each container provides its image ID as context data using `--custom-contextmenu-data` +4. The backend receives the image ID in click handlers and can perform specific operations +5. The same menu is reused for all images, but the context data tells us which image to operate on + +This pattern is particularly powerful for: +- Data grids where rows need specific operations +- File managers where files need context-specific actions +- Design tools where different elements need different operations +- Any component where the same operations apply to multiple instances diff --git a/docs/src/content/docs/guides/msix-packaging.mdx b/docs/src/content/docs/guides/msix-packaging.mdx new file mode 100644 index 000000000..8b9ac57a1 --- /dev/null +++ b/docs/src/content/docs/guides/msix-packaging.mdx @@ -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-.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` | `.msix` | Output path / filename. | +| `--publisher` | `CN=` | 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! diff --git a/docs/src/content/docs/guides/panic-handling.mdx b/docs/src/content/docs/guides/panic-handling.mdx new file mode 100644 index 000000000..f252f0dc3 --- /dev/null +++ b/docs/src/content/docs/guides/panic-handling.mdx @@ -0,0 +1,116 @@ +--- +title: Handling Panics +description: How to handle panics in your Wails application +--- + +In Go applications, panics can occur during runtime when something unexpected happens. This guide explains how to handle panics both in general Go code and specifically in your Wails application. + +## Understanding Panics in Go + +Before diving into Wails-specific panic handling, it's essential to understand how panics work in Go: + +1. Panics are for unrecoverable errors that shouldn't happen during normal operation +2. When a panic occurs in a goroutine, only that goroutine is affected +3. Panics can be recovered using `defer` and `recover()` + +Here's a basic example of panic handling in Go: + +```go +func doSomething() { + // Deferred functions run even when a panic occurs + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered from panic: %v\n", r) + } + }() + + // Your code that might panic + panic("something went wrong") +} +``` + +For more detailed information about panic and recover in Go, see the [Go Blog: Defer, Panic, and Recover](https://go.dev/blog/defer-panic-and-recover). + +## Panic Handling in Wails + +Wails automatically handles panics that occur in your Service methods when they are called from the frontend. This means you don't need to add panic recovery to these methods - Wails will catch the panic and process it through your configured panic handler. + +The panic handler is specifically designed to catch: +- Panics in bound service methods called from the frontend +- Internal panics from the Wails runtime + +For other scenarios, such as background goroutines or standalone Go code, you should handle panics yourself using Go's standard panic recovery mechanisms. + +## The PanicDetails Struct + +When a panic occurs, Wails captures important information about the panic in a `PanicDetails` struct: + +```go +type PanicDetails struct { + StackTrace string // The stack trace of where the panic occurred. Potentially trimmed to provide more context + Error error // The error that caused the panic + Time time.Time // The time when the panic occurred + FullStackTrace string // The complete stack trace including runtime frames +} +``` + +This structure provides comprehensive information about the panic: +- `StackTrace`: A formatted string showing the call stack that led to the panic +- `Error`: The actual error or panic message +- `Time`: The exact time when the panic occurred +- `FullStackTrace`: The complete stack trace including runtime frames + +:::note[Panics in Service Code] + +When panics are caught in your Service code after being called from the frontend, the stack trace is trimmed to focus on exactly where in your code the panic occurred. +If you want to see the full stack trace, you can use the `FullStackTrace` field. + +::: + +## Default Panic Handler + +If you don't specify a custom panic handler, Wails will use its default handler which outputs error information in a formatted log message and then quits. +For example: + +``` +************************ FATAL ****************************** +* There has been a catastrophic failure in your application * +********************* Error Details ************************* +panic error: oh no! something went wrong deep in my service! :( +main.(*WindowService).call2 + at E:/wails/v3/examples/panic-handling/main.go:23 +main.(*WindowService).call1 + at E:/wails/v3/examples/panic-handling/main.go:19 +main.(*WindowService).GeneratePanic + at E:/wails/v3/examples/panic-handling/main.go:15 +************************************************************* +``` + +## Custom Panic Handler + +You can implement your own panic handler by setting the `PanicHandler` option when creating your application. Here's an example: + +```go +app := application.New(application.Options{ + Name: "My App", + PanicHandler: func(panicDetails *application.PanicDetails) { + fmt.Printf("*** Custom Panic Handler ***\n") + fmt.Printf("Time: %s\n", panicDetails.Time) + fmt.Printf("Error: %s\n", panicDetails.Error) + fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) + fmt.Printf("Full Stacktrace: %s\n", panicDetails.FullStackTrace) + + // You could also: + // - Log to a file + // - Send to a crash reporting service + // - Show a user-friendly error dialog + // - Attempt to recover or restart the application + }, +}) +``` + +For a complete working example of panic handling in a Wails application, see the panic-handling example in `v3/examples/panic-handling`. + +## Final Notes + +Remember that the Wails panic handler is specifically for managing panics in bound methods and internal runtime errors. For other parts of your application, you should use Go's standard error handling patterns and panic recovery mechanisms where appropriate. As with all Go applications, it's better to prevent panics through proper error handling where possible. diff --git a/docs/src/content/docs/guides/signing.mdx b/docs/src/content/docs/guides/signing.mdx new file mode 100644 index 000000000..b5de768c9 --- /dev/null +++ b/docs/src/content/docs/guides/signing.mdx @@ -0,0 +1,228 @@ +--- +title: Code Signing +description: Guide for signing your Wails applications on macOS and Windows +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; +import { Card, CardGrid } from '@astrojs/starlight/components'; + +# Code Signing Your Application + +This guide covers how to sign your Wails applications for both macOS and Windows, with a focus on automated signing using GitHub Actions. + + + + Sign your Windows executables with certificates + + + Sign and notarize your macOS applications + + + +## Windows Code Signing + + +1. **Obtain a Code Signing Certificate** + - Get from a trusted provider listed on [Microsoft's documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate) + - Standard code signing certificate is sufficient (EV not required) + - Test signing locally before setting up CI + +2. **Prepare for GitHub Actions** + - Convert your certificate to Base64 + - Store in GitHub Secrets + - Set up signing workflow + +3. **Configure GitHub Actions** + ```yaml + name: Sign Windows Binary + + on: + workflow_dispatch: + release: + types: [created] + + jobs: + sign: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Import Certificate + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value ${{ secrets.WINDOWS_CERTIFICATE }} + certutil -decode certificate\certificate.txt certificate\certificate.pfx + + - name: Sign Binary + run: | + & 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool.exe' sign /f certificate\certificate.pfx /t http://timestamp.sectigo.com /p ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} /v /fd sha256 .\build\bin\app.exe + ``` + + +### Important Windows Parameters + +- **Signing Algorithm**: Usually `sha256` +- **Timestamp Server**: Valid timestamping server URL +- **Certificate Password**: Stored in GitHub Secrets +- **Binary Path**: Path to your compiled executable + +## macOS Code Signing + + +1. **Prerequisites** + - Apple Developer Account + - Developer ID Certificate + - App Store Connect API Key + - [gon](https://github.com/mitchellh/gon) for notarization + +2. **Certificate Setup** + - Generate Developer ID Certificate + - Download and install certificate + - Export certificate for CI + +3. **Configure Notarization** + ```json title="gon-sign.json" + { + "source": ["./build/bin/app"], + "bundle_id": "com.company.app", + "apple_id": { + "username": "dev@company.com", + "password": "@env:AC_PASSWORD" + }, + "sign": { + "application_identity": "Developer ID Application: Company Name" + } + } + ``` + +4. **GitHub Actions Configuration** + ```yaml + name: Sign macOS Binary + + on: + workflow_dispatch: + release: + types: [created] + + jobs: + sign: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: Import Certificate + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + run: | + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain + + - name: Sign and Notarize + env: + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + run: | + gon -log-level=info ./build/darwin/gon-sign.json + ``` + + +### Important macOS Parameters + +- **Bundle ID**: Unique identifier for your app +- **Developer ID**: Your Developer ID Application certificate +- **Apple ID**: Developer account credentials +- **ASC API Key**: App Store Connect API credentials + +## Best Practices + +1. **Security** + - Store all credentials in GitHub Secrets + - Use environment variables for sensitive data + - Regularly rotate certificates and credentials + +2. **Workflow** + - Test signing locally first + - Use conditional signing based on platform + - Implement proper error handling + +3. **Verification** + - Verify signatures after signing + - Test notarization process + - Check timestamp validity + +## Troubleshooting + +### Windows Issues +- Certificate not found +- Invalid timestamp server +- Signing tool errors + +### macOS Issues +- Keychain access issues +- Notarization failures +- Certificate validation errors + +## Complete GitHub Actions Workflow + +```yaml +name: Sign Binaries + +on: + workflow_dispatch: + release: + types: [created] + +jobs: + sign: + strategy: + matrix: + platform: [windows-latest, macos-latest] + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v3 + + # Windows Signing + - name: Sign Windows Binary + if: matrix.platform == 'windows-latest' + env: + CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value $env:CERTIFICATE + certutil -decode certificate\certificate.txt certificate\certificate.pfx + & 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool.exe' sign /f certificate\certificate.pfx /t http://timestamp.sectigo.com /p $env:CERTIFICATE_PASSWORD /v /fd sha256 .\build\bin\app.exe + + # macOS Signing + - name: Sign macOS Binary + if: matrix.platform == 'macos-latest' + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + run: | + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain + gon -log-level=info ./build/darwin/gon-sign.json +``` + +## Additional Resources + +- [Apple Code Signing Documentation](https://developer.apple.com/support/code-signing/) +- [Microsoft Code Signing Documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate) +- [Gon Documentation](https://github.com/mitchellh/gon) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) diff --git a/docs/src/content/docs/guides/single-instance.mdx b/docs/src/content/docs/guides/single-instance.mdx new file mode 100644 index 000000000..25b7fac47 --- /dev/null +++ b/docs/src/content/docs/guides/single-instance.mdx @@ -0,0 +1,119 @@ +--- +title: Single Instance +description: Limiting your app to a single running instance +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Single instance locking is a mechanism that prevents multiple instances of your app from running at the same time. +It is useful for apps that are designed to open files from the command line or from the OS file explorer. + + +## Usage + +To enable single instance functionality in your app, provide a `SingleInstanceOptions` struct when creating your application: + +```go +app := application.New(application.Options{ + // ... other options ... + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + log.Printf("Second instance launched with args: %v", data.Args) + log.Printf("Working directory: %s", data.WorkingDir) + log.Printf("Additional data: %v", data.AdditionalData) + }, + // Optional: Pass additional data to second instance + AdditionalData: map[string]string{ + "launchtime": time.Now().String(), + }, + }, +}) +``` + +The `SingleInstanceOptions` struct has the following fields: + +- `UniqueID`: A unique identifier for your application. This should be a unique string, typically in reverse domain notation (e.g., "com.company.appname"). +- `EncryptionKey`: Optional 32-byte array for encrypting data passed between instances using AES-256-GCM. If provided as a non-zero array, all communication between instances will be encrypted. +- `OnSecondInstanceLaunch`: A callback function that is called when a second instance of your app is launched. The callback receives a `SecondInstanceData` struct containing: + - `Args`: The command line arguments passed to the second instance + - `WorkingDir`: The working directory of the second instance + - `AdditionalData`: Any additional data passed from the second instance (if provided) +- `AdditionalData`: Optional map of string key-value pairs that will be passed to the first instance when subsequent instances are launched + +:::danger[Warning] +The Single Instance feature implements an optional encryption protocol using AES-256-GCM. Without encryption enabled, +data passed between instances is not secure. When using the single instance feature without encryption, +your app should treat any data passed to it from second instance callback as untrusted. +You should verify that args that you receive are valid and don't contain any malicious data. +::: + +### Secure Communication + +To enable secure communication between instances, provide a 32-byte encryption key. This key must be the same for all instances of your application: + +```go +// Define your encryption key (must be exactly 32 bytes) +var encryptionKey = [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +} + +// Use the key in SingleInstanceOptions +SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + // Enable encryption for instance communication + EncryptionKey: encryptionKey, + // ... other options ... +} +``` + +:::tip[Security Best Practices] +- Use a unique key for your application +- Store the key securely if loading it from configuration +- Do not use the example key shown above - create your own! +::: + +### Window Management + +When handling second instance launches, you'll often want to bring your application window to the front. You can do this using the window's `Focus()` method. If your window is minimized, you may need to restore it first: + +```go + + var mainWindow *application.WebviewWindow + + SingleInstance: &application.SingleInstanceOptions{ + // Other options... + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + // Focus the window if needed + if mainWindow != nil { + mainWindow.Restore() + mainWindow.Focus() + } + }, + } +``` + +## How it works + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via [NSDistributedNotificationCenter](https://developer.apple.com/documentation/foundation/nsdistributednotificationcenter) + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via a shared window using [SendMessage](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage) + + + + + Single instance lock using [dbus](https://www.freedesktop.org/wiki/Software/dbus/). The dbus name is generated from the unique id that you provide. Data is passed to the first instance via [dbus](https://www.freedesktop.org/wiki/Software/dbus/) + + + diff --git a/docs/src/content/docs/guides/windows-uac.mdx b/docs/src/content/docs/guides/windows-uac.mdx new file mode 100644 index 000000000..5bb380179 --- /dev/null +++ b/docs/src/content/docs/guides/windows-uac.mdx @@ -0,0 +1,151 @@ +--- +title: Windows UAC Configuration +sidebar: + order: 11 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +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 + + + + + + + +``` + +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" + + + + + + + +``` + +### Examples + +#### Standard Application (Default) +Most applications should use the default `asInvoker` level: + +```xml + +``` + +#### System Utility +Applications that need elevated access when available: + +```xml + +``` + +#### Administrative Tool +Applications that always require administrator privileges: + +```xml + +``` + +## 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 \ No newline at end of file diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 000000000..3b71e86be --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,114 @@ +--- +title: Welcome to Wails +description: "Create beautiful applications using Go" +banner: + content: | + This site is for v3 Alpha and is under construction. v2 is the stable release. +template: splash +hero: + tagline: + A powerful framework for building desktop applications using Go and modern web + technologies. + image: + dark: ../../assets/wails-logo-dark.svg + light: ../../assets/wails-logo-light.svg + alt: Wails Logo + + actions: + - text: Getting Started + link: /getting-started/installation + icon: right-arrow + - text: Sponsor + link: https://github.com/sponsors/leaanthony + icon: heart + class: + - is-sponsor +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; +import CardAnimation from '../../components/CardAnimation.astro'; + + :::danger[Alpha Status] + Wails v3 is currently in ALPHA. Please follow our [feedback guide](/feedback) to help us improve the project. + This site is still under construction. + ::: + +
+ +
+ + + Build desktop applications that combine the power of Go with modern web technologies: + + - **Native Performance**: Direct in-memory Go-to-Frontend communication + - **Cross Platform**: One codebase for Windows, macOS and Linux + - **Modern Stack**: Use any modern web framework (React, Vue, Svelte, etc.) + - **Native APIs**: Access OS-level features directly from Go + - **Developer Experience**: Hot reload, native dialogs, system tray, and more + - **Small Footprint**: Lightweight alternative to Electron + + + + Before you begin, ensure you have: + + - Go 1.23 or later installed + - Latest version of npm installed (if you want to use the templates) + - Basic knowledge of Go programming + + Check our [detailed prerequisites](/getting-started/installation#dependencies) for a complete list. + + + + ```bash + # Install wails + go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + # Create a new project + wails3 init -n myproject + + # Run your project + cd myproject + wails3 dev + ``` + + + + Wails v3 is currently in Alpha. While it's stable enough for testing and + development, there might be breaking changes before the final release. Your feedback + and contributions are welcome! + + + + - **Multiple Windows Support**: Create and manage multiple windows in a single application + - **Improved API Design**: New procedural approach for more flexibility + - **Enhanced Bindings**: Sophisticated static analyzer for Go-to-Frontend communication + - **Better Build Tooling**: A new build system based on [Taskfile](https://taskfile.dev/) + - **New Linux Packaging**: Support for deb, rpm, arch linux, and AppImage + - **New Templates**: Create applications with a single command using our pre-built templates + + + + Ready to build your first Wails application? Check out our + [installation guide](/getting-started/installation) to get started. + + + + We value your feedback and have a [detailed guide on providing it](/feedback). + Use this guide to: + + - Report bugs + - Request features + - Get help from the community + - Contribute to the project + + Join our [Discord community](https://discord.gg/bdj28QNHmT) + or visit our [GitHub repository](https://github.com/wailsapp/wails) to: + + + + Please note that v3 is currently in Alpha. While we're working hard to ensure + stability, there might be breaking changes before the final release. For production + use, please use [Wails v2](https://wails.io). + + +
diff --git a/docs/src/content/docs/learn/advanced-binding.mdx b/docs/src/content/docs/learn/advanced-binding.mdx new file mode 100644 index 000000000..82fbb7dec --- /dev/null +++ b/docs/src/content/docs/learn/advanced-binding.mdx @@ -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` 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 { + "Data": T; + "Error": string; + + constructor(source: Partial> = {}) { + if (!("Data" in source)) { + this["Data"] = null as any; + } + if (!("Error" in source)) { + this["Error"] = ""; + } + + Object.assign(this, source); + } + + static createFrom(source: string | object = {}): Result { + let parsedSource = typeof source === "string" ? JSON.parse(source) : source; + return new Result(parsedSource as Partial>); + } +} +``` + +### 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; + 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} +//wails:inject j*: */ +//wails:inject j*:export async function CustomMethod(arg) { +//wails:inject t*:export async function CustomMethod(arg: string): Promise { +//wails:inject await InternalMethod("Hello " + arg + "!"); +//wails:inject } +type Service struct{} +``` + +This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language. diff --git a/docs/src/content/docs/learn/application-menu.mdx b/docs/src/content/docs/learn/application-menu.mdx new file mode 100644 index 000000000..f7e659cbf --- /dev/null +++ b/docs/src/content/docs/learn/application-menu.mdx @@ -0,0 +1,262 @@ +--- +title: Application Menu +sidebar: + order: 53 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Application menus provide the main menu bar interface for your application. They appear at the top of the window on Windows and Linux, and at the top of the screen on macOS. + +## Creating an Application Menu + +Create a new application menu using the `New` method from the Menu manager: + +```go +menu := app.Menu.New() +``` + +## Setting the Menu + +The way to set the menu varies on the platform: + + + + + 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) + ``` + + + + + + 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) + ``` + + + + + + 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) + ``` + + + + + +## Menu Roles + +Wails provides predefined menu roles that automatically create platform-appropriate menu structures: + +```go +// Add standard application menu on macOS +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) +} + +// Add standard menus +menu.AddRole(application.FileMenu) +menu.AddRole(application.EditMenu) +menu.AddRole(application.WindowMenu) +menu.AddRole(application.HelpMenu) +``` + +:::note[Platform Behaviour] +The AppMenu role is specific to macOS and provides the standard application menu containing About, Preferences, and Quit items. +::: + +### Available Roles + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Standard application menu | macOS only | +| `FileMenu` | File operations menu | All platforms | +| `EditMenu` | Text editing operations | All platforms | +| `WindowMenu` | Window management | All platforms | +| `HelpMenu` | Help and information | All platforms | + +## Custom Menus + +Create custom menus by adding items directly: + +```go +// Add a custom menu +customMenu := menu.AddSubmenu("Tools") +customMenu.Add("Settings").OnClick(func(ctx *application.Context) { + // Show settings dialogue +}) +``` + +:::tip[Menu Items] +For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation. +::: + +## Window Control + +Menu items can control the application windows: + +```go +viewMenu := menu.AddSubmenu("View") +viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) { + window := app.Window.Current() + if window.Fullscreen() { + window.SetFullscreen(false) + } else { + window.SetFullscreen(true) + } +}) +``` + +## Dynamic Menus + +Menus can be updated dynamically based on application state: + +```go +projectMenu := menu.AddSubmenu("Project") +saveItem := projectMenu.Add("Save Project") + +// Update based on state +saveItem.OnClick(func(ctx *application.Context) { + if projectSaved { + saveItem.SetEnabled(false) + saveItem.SetLabel("Project Saved") + } + menu.Update() +}) +``` + +## Platform-Specific Considerations + + + + + On macOS, menus are deeply integrated with the system: + + - Menus appear in the system menu bar at the top of the screen + - The application menu (โŒ˜) is required and should be added using `menu.AddRole(application.AppMenu)` + - Standard keyboard shortcuts are automatically handled + - Menu styling follows system appearance + - The "About" menu item appears in the application menu + - Preferences are typically placed in the application menu + + + + + + On Windows, menus follow the traditional Windows UI guidelines: + + - Menus appear in the application window's title bar + - Standard keyboard shortcuts should be explicitly set using `SetAccelerator` + - Menu styling matches the Windows theme + - The "About" menu item typically appears in the Help menu + - Settings/Preferences are typically placed in the Tools menu + + + + + + On Linux, menu behaviour depends on the desktop environment: + + - Menu appearance adapts to the desktop environment's theme + - Some desktop environments (like Unity) support global menu bars + - Menu placement follows the desktop environment's conventions + - Keyboard shortcuts should be explicitly set + - Settings are typically placed in the Edit menu + + + + +## Best Practices + +1. Use standard menu roles where appropriate +2. Follow platform-specific menu conventions +3. Provide keyboard shortcuts for common actions +4. Keep menu structures shallow and organised +5. Update menu items to reflect application state +6. Use clear, concise menu labels +7. Group related items logically + +:::danger[Warning] +Always test menu functionality across all target platforms to ensure consistent behaviour and appearance. +::: + +:::tip[Pro Tip] +Consider using the `app.Window.Current()` method in menu handlers to affect the active window, rather than storing window references. +::: + +## Complete Example + +Here's a comprehensive example demonstrating various menu features: + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Menu Demo", + }) + + // Create main menu + menu := app.Menu.New() + + // Add platform-specific application menu + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // Add standard menus + fileMenu := menu.AddRole(application.FileMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.HelpMenu) + + // Add custom menu + toolsMenu := menu.AddSubmenu("Tools") + + // Add checkbox item + toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) { + isDark := ctx.ClickedMenuItem().Checked() + // Toggle theme + }) + + // Add radio group + toolsMenu.AddRadio("Small Text", true).OnClick(handleFontSize) + toolsMenu.AddRadio("Medium Text", false).OnClick(handleFontSize) + toolsMenu.AddRadio("Large Text", false).OnClick(handleFontSize) + + // Add submenu + advancedMenu := toolsMenu.AddSubmenu("Advanced") + advancedMenu.Add("Configure...").OnClick(func(ctx *application.Context) { + // Show configuration + }) + + // Set the menu + app.Menu.Set(menu) + + // Create main window + app.NewWebviewWindow() + + err := app.Run() + if err != nil { + panic(err) + } +} + +func handleFontSize(ctx *application.Context) { + size := ctx.ClickedMenuItem().Label() + // Update font size +} diff --git a/docs/src/content/docs/learn/badges.mdx b/docs/src/content/docs/learn/badges.mdx new file mode 100644 index 000000000..3effaf7cc --- /dev/null +++ b/docs/src/content/docs/learn/badges.mdx @@ -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 + + + + + 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 + + + + + + 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) + + + + + + On Linux: + + - Badge functionality is not available + + + + +## 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 +} +``` diff --git a/docs/src/content/docs/learn/binding-best-practices.mdx b/docs/src/content/docs/learn/binding-best-practices.mdx new file mode 100644 index 000000000..4536fc3c9 --- /dev/null +++ b/docs/src/content/docs/learn/binding-best-practices.mdx @@ -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 +} +``` diff --git a/docs/src/content/docs/learn/binding-system.mdx b/docs/src/content/docs/learn/binding-system.mdx new file mode 100644 index 000000000..13ce15932 --- /dev/null +++ b/docs/src/content/docs/learn/binding-system.mdx @@ -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 + + +- internal/generator/ + - collect/ # Package analysis and information extraction + - config/ # Configuration structures and interfaces + - render/ # Code generation for JS/TS + + +## 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[]` | +| `map[K]V` | `Object` | `Record` | +| `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: + +``` + + + +
+

Application Report

+

Generated: {{.Timestamp}}

+
+
+ + {{range .Items}} +

{{.}}

+ {{end}} +
+ +` + + // 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 + + + + + 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 + + + + + + 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 + + + + + + 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 + + + + +## 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 := ` + + + + Demo Report + + + +
+

Application Report

+

This is a sample report generated by the application.

+
+
+

Report Details

+

This report was generated to demonstrate browser integration.

+
+ +` + + 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. +::: \ No newline at end of file diff --git a/docs/src/content/docs/learn/build.mdx b/docs/src/content/docs/learn/build.mdx new file mode 100644 index 000000000..e45c6e3b9 --- /dev/null +++ b/docs/src/content/docs/learn/build.mdx @@ -0,0 +1,258 @@ +--- +title: Build System +sidebar: + order: 40 +--- + +import { FileTree } from "@astrojs/starlight/components"; + +## Overview + +The Wails build system is a flexible and powerful tool designed to streamline +the build process for your Wails applications. It leverages +[Task](https://taskfile.dev), a task runner that allows you to define and run +tasks easily. While the v3 build system is the default, Wails encourages a +"bring your own tooling" approach, allowing developers to customize their build +process as needed. + +Learn more about how to use Task in the +[official documentation](https://taskfile.dev/usage/). + +## Task: The Heart of the Build System + +[Task](https://taskfile.dev) is a modern alternative to Make, written in Go. It +uses a YAML file to define tasks and their dependencies. In the Wails build +system, [Task](https://taskfile.dev) plays a central role in orchestrating the +build process. + +The main `Taskfile.yml` is located in the project root, while platform-specific +tasks are defined in `build//Taskfile.yml` files. A common `Taskfile.yml` +file in the `build` directory contains common tasks that are shared across +platforms. + + + +- Project Root + - Taskfile.yml + - build + - windows/Taskfile.yml + - darwin/Taskfile.yml + - linux/Taskfile.yml + - Taskfile.yml + + + +## Taskfile.yml + +The `Taskfile.yml` file in the project root is the main entry point for the build system. It defines +the tasks and their dependencies. Here's the default `Taskfile.yml` file: + +```yaml +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: "myproject" + 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}} + + +``` + +## Platform-Specific Taskfiles + +Each platform has its own Taskfile, located in the platform directories beneath the `build` directory. These +files define the core tasks for that platform. Each taskfile includes common tasks from the `build/Taskfile.yml` file. + +### Windows + +Location: `build/windows/Taskfile.yml` + +The Windows-specific Taskfile includes tasks for building, packaging, and +running the application on Windows. Key features include: + +- Building with optional production flags +- Generating `.ico` icon file +- Generating Windows `.syso` file +- Creating an NSIS installer for packaging + +### Linux + +Location: `build/linux/Taskfile.yml` + +The Linux-specific Taskfile includes tasks for building, packaging, and running +the application on Linux. Key features include: + +- Building with optional production flags +- Creating an AppImage, deb, rpm, and Arch Linux packages +- Generating `.desktop` file for Linux applications + +### macOS + +Location: `build/darwin/Taskfile.yml` + +The macOS-specific Taskfile includes tasks for building, packaging, and running +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 + +The `wails3 task` command is an embedded version of [Taskfile](https://taskfile.dev), which executes +the tasks defined in your `Taskfile.yml`. + +The `wails3 build` and `wails3 package` commands are aliases for +`wails3 task build` and `wails3 task package` respectively. When you run these +commands, Wails internally translates them to the appropriate task execution: + +- `wails3 build` โ†’ `wails3 task build` +- `wails3 package` โ†’ `wails3 task package` + +## Common Build Process + +Across all platforms, the build process typically includes the following steps: + +1. Tidying Go modules +2. Building the frontend +3. Generating icons +4. Compiling the Go code with platform-specific flags +5. Packaging the application (platform-specific) + +## Customising the Build Process + +While the v3 build system provides a solid default configuration, you can easily +customise it to fit your project's needs. By modifying the `Taskfile.yml` and +platform-specific Taskfiles, you can: + +- Add new tasks +- Modify existing tasks +- Change the order of task execution +- Integrate with other tools and scripts + +This flexibility allows you to tailor the build process to your specific +requirements while still benefiting from the structure provided by the Wails +build system. + +:::tip[Learning Taskfile] +We highly recommend reading the [Taskfile](https://taskfile.dev) documentation to +understand how to use Taskfile effectively. +You can find out which version of Taskfile is embedded in the Wails CLI by running `wails3 task --version`. +::: + +## Development Mode + +The Wails build system includes a powerful development mode that enhances the +developer experience by providing live reloading and hot module replacement. +This mode is activated using the `wails3 dev` command. + +### How It Works + +When you run `wails3 dev`, the following process occurs: + +1. The command checks for an available port, defaulting to 9245 if not + specified. +2. It sets up the environment variables for the frontend dev server (Vite). +3. It starts the file watcher using the [refresh](https://github.com/atterpac/refresh) library. + +The [refresh](https://github.com/atterpac/refresh) library is responsible for +monitoring file changes and triggering rebuilds. It uses the configuration defined under the `dev_mode` key in the `./build/config.yaml` file. +It may be configured to ignore certain directories and files, to determine which files to watch and what actions to take when changes are detected. +The default configuration works pretty well, but feel free to customise it to your needs. + +### Configuration + +Here's an example of its structure: + +```yaml +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 +``` + +This configuration file allows you to: + +- Set the root path for file watching +- Configure logging level +- Set a debounce time for file change events +- Ignore specific directories, files, or file extensions +- Define commands to execute on file changes + +### Customising Development Mode + +You can customise the development mode experience by modifying these values in the `config.yaml` file. + +Some ways to customise include: + +1. Changing the watched directories or files +2. Adjusting the debounce time to control how quickly the system responds to + changes +3. Adding or modifying the execute commands to fit your project's needs + +### Using a browser for development + +Whilst Wails v2 fully supported the use of a browser for development, it caused a lot +of confusion. Applications that would work in the browser would not necessarily +work in the desktop application, as not all browser APIs are available in webviews. + +For UI-focused development work, you still have the flexibility to use a browser +in v3, by accessing the Vite URL at `http://localhost:9245` in dev mode. This +gives you access to powerful browser dev tools while working on styling and +layout. Be aware that Go bindings *will not work* in this mode. +When you're ready to test functionality like bindings and events, simply +switch to the desktop view to ensure everything works perfectly in the +production environment. diff --git a/docs/src/content/docs/learn/clipboard.mdx b/docs/src/content/docs/learn/clipboard.mdx new file mode 100644 index 000000000..39e14c52f --- /dev/null +++ b/docs/src/content/docs/learn/clipboard.mdx @@ -0,0 +1,135 @@ +--- +title: Clipboard +sidebar: + order: 50 +--- + +The Wails Clipboard API provides a simple interface for interacting with the system clipboard. It allows you to read from and write to the clipboard, whilst supporting text data. + +## Accessing the Clipboard + +The clipboard can be accessed through the application instance: + +```go +clipboard := app.Clipboard +``` + +## Setting Text + +To set text to the clipboard, utilise the `SetText` method: + +```go +success := app.Clipboard.SetText("Hello World") +if !success { + // Handle error +} +``` + +The `SetText` method returns a boolean indicating whether the operation was successful. + +:::tip[Empty Text] +Setting an empty string (`""`) effectively clears the text content from the clipboard. +::: + +## Getting Text + +To retrieve text from the clipboard, utilise the `Text` method: + +```go +text, ok := app.Clipboard.Text() +if !ok { + // Handle error +} else { + // Use the text +} +``` + +The `Text` method returns two values: +- The text content from the clipboard (string) +- A boolean indicating whether the operation was successful + +:::note[Platform Behaviour] +The clipboard behaviour might vary slightly amongst operating systems. Always check the return values to ensure operations were successful. +::: + +## Example + +Here's a complete example showing how to create a menu-driven application that demonstrates clipboard operations: + +```go +package main + +import ( + "log" + "runtime" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Clipboard Demo", + Description: "A demo of the clipboard API", + Assets: application.AlphaAssets, + }) + + // Create a custom menu + menu := app.Menu.New() + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // 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") + if !success { + app.Dialog.Info().SetMessage("Failed to set clipboard text").Show() + } + }) + + getClipboardMenu := menu.AddSubmenu("Get Clipboard") + getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) { + result, ok := app.Clipboard.Text() + if !ok { + app.Dialog.Info().SetMessage("Failed to get clipboard text").Show() + } else { + app.Dialog.Info().SetMessage("Got:\n\n" + result).Show() + } + }) + + clearClipboardMenu := menu.AddSubmenu("Clear Clipboard") + clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) { + success := app.Clipboard.SetText("") + if success { + app.Dialog.Info().SetMessage("Clipboard text cleared").Show() + } else { + app.Dialog.Info().SetMessage("Clipboard text not cleared").Show() + } + }) + + app.Menu.Set(menu) + app.NewWebviewWindow() + + err := app.Run() + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +:::danger[Warning] +Always handle clipboard operation failures gracefully, as they can fail due to various system-level reasons such as permissions or resource constraints. +::: + +## Best Practices + +1. Always check the return values of clipboard operations +2. Handle failures gracefully with appropriate user feedback +3. Clear sensitive data from the clipboard when your application exits if it was responsible for putting it there +4. Consider implementing a timeout mechanism for clipboard operations in critical sections of your application + +:::tip[Pro Tip] +Whilst working with the clipboard in a production environment, consider implementing retry logic for critical clipboard operations, as they can occasionally fail due to temporary system conditions. +::: diff --git a/docs/src/content/docs/learn/context-menu.mdx b/docs/src/content/docs/learn/context-menu.mdx new file mode 100644 index 000000000..e5bec1e6e --- /dev/null +++ b/docs/src/content/docs/learn/context-menu.mdx @@ -0,0 +1,237 @@ +--- +title: Context Menus +sidebar: + order: 51 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element. + +## Creating a Context Menu + +To create a context menu, use the `Add` method from the ContextMenu manager: + +```go +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. + +## Adding Menu Items + +You can add items to your context menu using the same methods as application menus. Here's a simple example: + +```go +contextMenu := application.NewContextMenu() +contextMenu.Add("Cut").OnClick(func(ctx *application.Context) { + // Handle cut action +}) +contextMenu.Add("Copy").OnClick(func(ctx *application.Context) { + // Handle copy action +}) +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] +For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation. +::: + +## Context Data + +Context menus can receive data from the HTML element that triggered them. This data can be accessed in the click handlers: + +```go +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 + +To associate a context menu with an HTML element, use the `--custom-contextmenu` and `--custom-contextmenu-data` CSS properties: + +```html +
+ Right click me! +
+``` + +- `--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: + +```html + +
+ No default menu here +
+ + +
+ Default menu always shown +
+ + +
+ Shows menu when appropriate +
+``` + +:::note[Smart Context Menu] +The `auto` setting enables "smart" context menu behaviour: +- Shows when text is selected +- Shows in text input fields +- Shows in editable content +- Hides in other contexts +::: + +## Updating Menu Items + +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() +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() +``` + +## Platform Considerations + + + + + On macOS, context menus follow system conventions: + + - Menus use native system animations and transitions + - Right-click is automatically mapped to Control+Click + - Menu styling automatically adapts to system appearance + - Standard text operations appear in the default context menu + - Context menus support native macOS scrolling behaviour + + + + + + On Windows, context menus integrate with the Windows UI: + + - Menus use the Windows native context menu style + - Right-click handling is native + - Menu appearance follows the Windows theme + - Default context menu includes standard Windows operations + - Context menus support Windows touch and pen input + + + + + + On Linux, context menu behaviour varies by desktop environment: + + - Menu styling adapts to the current desktop theme + - Right-click behaviour follows system settings + - Default context menu content may vary by environment + - Menu positioning follows desktop environment conventions + - GTK/Qt integration depends on the environment + + + + +:::tip[Pro Tip] +Consider using different context menus for different types of elements in your application. This allows you to provide context-specific actions that make sense for each element type. +::: + +## Best Practices + +1. Keep context menus focused and relevant to the clicked element +2. Use clear, concise labels for menu items +3. Group related items together +4. Consider using separators to organise menu items +5. Provide keyboard shortcuts for common actions +6. Update menu items dynamically based on application state +7. Handle errors gracefully when processing context data + +:::danger[Warning] +Always validate context data received from the frontend before using it in your application logic, as it could be manipulated by users. +::: + +## Example + +Here's a complete example demonstrating context menu features: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Context Menu Demo", + }) + + // Create a context menu + contextMenu := app.ContextMenu.New() + + // Add items that respond to context data + clickMe := contextMenu.Add("Click to show context data") + dataLabel := contextMenu.Add("Current data: None") + + clickMe.OnClick(func(ctx *application.Context) { + data := ctx.ContextMenuData() + dataLabel.SetLabel("Current data: " + data) + contextMenu.Update() + }) + + // Register the context menu with the manager + app.ContextMenu.Add("test", contextMenu) + + window := app.NewWebviewWindow() + window.SetTitle("Context Menu Demo") + + err := app.Run() + if err != nil { + panic(err) + } +} +``` + +Associated HTML: + +```html +
+ Right click me to see the custom menu! +
+ +
+ No context menu here +
+ +
+

Select this text to see the default menu

+ +
diff --git a/docs/src/content/docs/learn/dialogs.mdx b/docs/src/content/docs/learn/dialogs.mdx new file mode 100644 index 000000000..783f7bf8b --- /dev/null +++ b/docs/src/content/docs/learn/dialogs.mdx @@ -0,0 +1,232 @@ +--- +title: Dialogs +sidebar: + order: 54 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides a comprehensive dialog system for displaying native system dialogs. These include informational messages, questions, file selection, and more. + +## Dialog Types + +### Information Dialog + +Display simple informational messages to users: + +```go +dialog := app.Dialog.Info() +dialog.SetTitle("Welcome") +dialog.SetMessage("Welcome to our application!") +dialog.Show() +``` + +### Question Dialog + +Present users with questions and customisable buttons: + +```go +dialog := app.Dialog.Question() +dialog.SetTitle("Save Changes") +dialog.SetMessage("Do you want to save your changes?") +dialog.AddButton("Save").OnClick(func() { + // Handle save +}) +saveButton := dialog.AddButton("Don't Save") +dialog.SetDefaultButton(saveButton) +dialog.Show() +``` + +### Error Dialog + +Display error messages: + +```go +dialog := app.Dialog.Error() +dialog.SetTitle("Error") +dialog.SetMessage("Failed to save file") +dialog.Show() +``` + +### File Dialogs + +#### Open File Dialog + +Allow users to select files to open: + +```go +dialog := app.Dialog.OpenFile() +dialog.SetTitle("Select Image") +dialog.SetFilters([]*application.FileFilter{ + { + DisplayName: "Images (*.png;*.jpg)", + Pattern: "*.png;*.jpg", + }, +}) + +// Single file selection +if path, err := dialog.PromptForSingleSelection(); err == nil { + // Use selected file path +} + +// Multiple file selection +if paths, err := dialog.PromptForMultipleSelection(); err == nil { + // Use selected file paths +} +``` + +#### Save File Dialog + +Allow users to choose where to save files: + +```go +dialog := app.Dialog.SaveFile() +dialog.SetTitle("Save Document") +dialog.SetDefaultFilename("document.txt") +dialog.SetFilters([]*application.FileFilter{ + { + DisplayName: "Text Files (*.txt)", + Pattern: "*.txt", + }, +}) + +if path, err := dialog.PromptForSingleSelection(); err == nil { + // Save file to selected path +} +``` + +## Dialog Customisation + +### Setting Icons + +Dialogs can use custom icons from the built-in icon set: + +```go +dialog := app.Dialog.Info() +dialog.SetIcon(icons.ApplicationDarkMode256) +``` + +### Window Attachment + +Dialogs can be attached to specific windows: + +```go +dialog := app.Dialog.Question() +dialog.AttachToWindow(app.Window.Current()) +dialog.Show() +``` + +### Button Customisation + +Create buttons with custom labels and actions: + +```go +dialog := app.Dialog.Question() +dialog.SetMessage("Choose an action") + +// Add buttons with custom handlers +dialog.AddButton("Save").OnClick(func() { + // Handle save +}) +dialog.AddButton("Don't Save").OnClick(func() { + // Handle don't save +}) +cancelButton := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelButton) // Set default button +``` + +## Platform Considerations + + + + + On macOS, dialogs follow system conventions: + + - Use system-standard dialog appearances + - Support keyboard navigation (Tab, Space, Return) + - Support standard keyboard shortcuts (โŒ˜+.) + - Automatically handle dark/light mode + - Support system accessibility features + - Position relative to parent window + + + + + + On Windows, dialogs integrate with the Windows UI: + + - Use Windows system dialog styles + - Support keyboard navigation (Tab, Space, Enter) + - Support Windows accessibility features + - Follow Windows dialog positioning rules + - Adapt to Windows theme settings + - Support high DPI displays + + + + + + On Linux, dialog behaviour depends on the desktop environment: + + - Use native dialog widgets when available + - Follow desktop environment theme + - Support keyboard navigation + - Adapt to desktop environment settings + - Position according to window manager rules + - Support desktop environment accessibility + + + + +## Directory Selection + +Allow users to select directories: + +```go +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 +} +``` + +## About Dialog + +Display application information: + +```go +app.Menu.ShowAbout() +``` + +## Best Practices + +1. Use appropriate dialog types for different scenarios: + - InfoDialog for general messages + - QuestionDialog for user decisions + - ErrorDialog for error messages + - FileDialog for file operations + +2. Provide clear and concise messages: + - Use descriptive titles + - Keep messages brief but informative + - Clearly state any required user action + +3. Handle dialog responses appropriately: + - Check for errors in file dialogs + - Provide feedback for user actions + - Handle cancellation gracefully + +4. Consider platform conventions: + - Follow platform-specific dialog patterns + - Use appropriate button ordering + - Respect system settings + +:::tip[Pro Tip] +When using file dialogs, always set appropriate filters to help users select the correct file types for your application. +::: + +:::danger[Warning] +Always handle potential errors from file and directory dialogs, as they may fail due to permissions or other system issues. +::: diff --git a/docs/src/content/docs/learn/environment.mdx b/docs/src/content/docs/learn/environment.mdx new file mode 100644 index 000000000..5903d9a59 --- /dev/null +++ b/docs/src/content/docs/learn/environment.mdx @@ -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 + + + + + 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") + } + } + ``` + + + + + + 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) + } + } + } + ``` + + + + + + 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) + } + } + ``` + + + + +## 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. +::: \ No newline at end of file diff --git a/docs/src/content/docs/learn/events.mdx b/docs/src/content/docs/learn/events.mdx new file mode 100644 index 000000000..87e00af75 --- /dev/null +++ b/docs/src/content/docs/learn/events.mdx @@ -0,0 +1,545 @@ +--- +title: Events +sidebar: + order: 55 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides a flexible event system that enables communication between different parts of your application. This includes both application-level and window-level events. + +## Application Events + +Application events are triggered by application-level state changes such as application startup, theme changes, and power events. You can listen for these events using the `OnApplicationEvent` method: + +```go +app.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(event *application.ApplicationEvent) { + app.Logger.Info("Application started!") +}) + +app.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *application.ApplicationEvent) { + app.Logger.Info("System theme changed!") + if event.Context().IsDarkMode() { + app.Logger.Info("System is now using dark mode!") + } else { + app.Logger.Info("System is now using light mode!") + } + }) +``` + +### Common Application Events + +Common application events are aliases for platform-specific application events. These events are triggered by application-level state +changes such as application startup, theme changes, and power events. + +Here is the same example as above, but using common application events to make it work across all platforms: + +```go +app.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + app.Logger.Info("Application started!") +}) + +app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + if event.Context().IsDarkMode() { + app.Logger.Info("System is now using dark mode!") + } else { + app.Logger.Info("System is now using light mode!") + } +}) +``` +#### Common Application Event List + +| Event Name | Description | +|---------------------------|----------------------------------------------------------------------------------------------------------| +| ApplicationOpenedWithFile | Application opened with a file. See [File Associations](/guides/file-associations) for more information. | +| ApplicationStarted | Application has started | +| ThemeChanged | System theme changed | + +### Platform-Specific Application Events + +Below is a list of all platform-specific application events. + + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | ApplicationDidBecomeActive | - | Application became active | + | ApplicationDidChangeBackingProperties | - | Application backing properties changed | + | ApplicationDidChangeEffectiveAppearance | ThemeChanged | Application appearance changed | + | ApplicationDidChangeIcon | - | Application icon changed | + | ApplicationDidChangeOcclusionState | - | Application occlusion state changed | + | ApplicationDidChangeScreenParameters | - | Screen parameters changed | + | ApplicationDidChangeStatusBarFrame | - | Status bar frame changed | + | ApplicationDidChangeStatusBarOrientation | - | Status bar orientation changed | + | ApplicationDidChangeTheme | ThemeChanged | System theme changed | + | ApplicationDidFinishLaunching | ApplicationStarted | Application finished launching | + | ApplicationDidHide | - | Application hidden | + | ApplicationDidResignActiveNotification | - | Application resigned active state | + | ApplicationDidUnhide | - | Application unhidden | + | ApplicationDidUpdate | - | Application updated | + | ApplicationShouldHandleReopen | - | Application should handle reopen | + | ApplicationWillBecomeActive | - | Application will become active | + | ApplicationWillFinishLaunching | - | Application will finish launching | + | ApplicationWillHide | - | Application will hide | + | ApplicationWillResignActiveNotification | - | Application will resign active state | + | ApplicationWillTerminate | - | Application will terminate | + | ApplicationWillUnhide | - | Application will unhide | + | ApplicationWillUpdate | - | Application will update | + + + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | APMPowerSettingChange | - | Power settings changed | + | APMPowerStatusChange | - | Power status changed | + | APMResumeAutomatic | - | System resuming automatically | + | APMResumeSuspend | - | System resuming from suspend | + | APMSuspend | - | System suspending | + | ApplicationStarted | ApplicationStarted | Application started | + | SystemThemeChanged | ThemeChanged | System theme changed | + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | ApplicationStartup | ApplicationStarted | Application started | + | SystemThemeChanged | ThemeChanged | System theme changed | + + + +## Window Events + +Window events are triggered by window-specific actions such as resizing, moving, or changing focus state. You can listen for these events using the `OnWindowEvent` method: + +```go +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window is closing!") +}) + +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window gained focus!") +}) +``` + +### Hooks vs Standard Listeners + +Wails provides two ways to handle window events: standard listeners (OnWindowEvent) and hooks (RegisterHook). The key differences are: + +1. **Execution Order**: Hooks are executed first and in the order they are registered, while standard listeners execute after Hooks and have no guaranteed order. +2. **Blocking**: Hooks are blocking and must complete before the next hook is executed. Standard listeners are non-blocking. +3. **Event Cancellation**: When cancelling an event in a Hook, it prevents it from propagating further. This is useful to prevent +default behaviour, such as closing a window. Cancelling an event in a standard listener will only prevent it from being emitted +from that point in time. + +In this example, the window will only close after the close button has been clicked three times, demonstrating how hooks can be used to control event flow. + +```go +// Hook - runs synchronously. The window will not close until the countdown reaches zero. +var countdown = 3 +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + countdown-- + if countdown == 0 { + app.Logger.Info("Window closing - countdown reached zero!") + return + } + app.Logger.Info("Preventing window from closing - countdown:", countdown) + e.Cancel() +}) +``` + +This next example demonstrates the execution order of hooks vs standard listeners. + +```go +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("I always run after hooks!") +}) + +// Multiple hooks are executed in order +window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("First focus hook - will always run first!") +}) +window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Second focus hook - will always run second!") +}) +``` + +This produces the following output: + +``` +INF First focus hook - will always run first! +INF Second focus hook - will always run second! +INF I always run after hooks! +``` + +### Common Window Events + +| Event Name | Description | +|--------------------|---------------------------| +| WindowClosing | Window is closing | +| WindowDidMove | Window moved | +| WindowDidResize | Window resized | +| WindowDPIChanged | Window DPI changed | +| WindowFilesDropped | Files dropped on window | +| WindowFocus | Window gained focus | +| WindowFullscreen | Window entered fullscreen | +| WindowHide | Window hidden | +| WindowLostFocus | Window lost focus | +| WindowMaximise | Window maximised | +| WindowMinimise | Window minimised | +| WindowRestore | Window restored | +| WindowRuntimeReady | Window runtime is ready | +| WindowShow | Window shown | +| WindowUnFullscreen | Window exited fullscreen | +| WindowUnMaximise | Window unmaximised | +| WindowUnMinimise | Window unminimised | +| WindowZoom | Window zoomed | +| WindowZoomIn | Window zoomed in | +| WindowZoomOut | Window zoomed out | +| WindowZoomReset | Window zoom reset | + +### Platform-Specific Window Events + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | WindowDidBecomeKey | WindowFocus | Window became key window | + | WindowDidBecomeMain | - | Window became main window | + | WindowDidBeginSheet | - | Sheet began | + | WindowDidChangeAlpha | - | Window alpha changed | + | WindowDidChangeBackingLocation | - | Window backing location changed | + | WindowDidChangeBackingProperties | - | Window backing properties changed | + | WindowDidChangeCollectionBehavior | - | Window collection behaviour changed | + | WindowDidChangeEffectiveAppearance | - | Window appearance changed | + | WindowDidChangeOcclusionState | - | Window occlusion state changed | + | WindowDidChangeOrderingMode | - | Window ordering mode changed | + | WindowDidChangeScreen | - | Window screen changed | + | WindowDidChangeScreenParameters | - | Window screen parameters changed | + | WindowDidChangeScreenProfile | - | Window screen profile changed | + | WindowDidChangeScreenSpace | - | Window screen space changed | + | WindowDidChangeScreenSpaceProperties | - | Window screen space properties changed | + | WindowDidChangeSharingType | - | Window sharing type changed | + | WindowDidChangeSpace | - | Window space changed | + | WindowDidChangeSpaceOrderingMode | - | Window space ordering mode changed | + | WindowDidChangeTitle | - | Window title changed | + | WindowDidChangeToolbar | - | Window toolbar changed | + | WindowDidDeminiaturize | WindowUnMinimise | Window unminimised | + | WindowDidEndSheet | - | Sheet ended | + | WindowDidEnterFullScreen | WindowFullscreen | Window entered fullscreen | + | WindowDidEnterVersionBrowser | - | Window entered version browser | + | WindowDidExitFullScreen | WindowUnFullscreen | Window exited fullscreen | + | WindowDidExitVersionBrowser | - | Window exited version browser | + | WindowDidExpose | - | Window exposed | + | WindowDidFocus | WindowFocus | Window gained focus | + | WindowDidMiniaturize | WindowMinimise | Window minimised | + | WindowDidMove | WindowDidMove | Window moved | + | WindowDidOrderOffScreen | - | Window ordered off screen | + | WindowDidOrderOnScreen | - | Window ordered on screen | + | WindowDidResignKey | - | Window resigned key window | + | WindowDidResignMain | - | Window resigned main window | + | WindowDidResize | WindowDidResize | Window resized | + | WindowDidUpdate | - | Window updated | + | WindowDidUpdateAlpha | - | Window alpha updated | + | WindowDidUpdateCollectionBehavior | - | Window collection behaviour updated | + | WindowDidUpdateCollectionProperties | - | Window collection properties updated | + | WindowDidUpdateShadow | - | Window shadow updated | + | WindowDidUpdateTitle | - | Window title updated | + | WindowDidUpdateToolbar | - | Window toolbar updated | + | WindowDidZoom | WindowZoom | Window zoomed | + | WindowFileDraggingEntered | - | File dragging entered window | + | WindowFileDraggingExited | - | File dragging exited window | + | WindowFileDraggingPerformed | - | File dragging performed | + | WindowHide | WindowHide | Window hidden | + | WindowMaximise | WindowMaximise | Window maximised | + | WindowShouldClose | WindowClosing | Window should close | + | WindowShow | WindowShow | Window shown | + | WindowUnMaximize | WindowUnMaximise | Window unmaximised | + | WindowZoomIn | WindowZoomIn | Window zoomed in | + | WindowZoomOut | WindowZoomOut | Window zoomed out | + | WindowZoomReset | WindowZoomReset | Window zoom reset | + |------------|--------------|-------------| + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | WebViewNavigationCompleted | - | WebView navigation completed | + | WindowActive | - | Window became active | + | WindowBackgroundErase | - | Window background needs erasing | + | WindowClickActive | - | Window clicked whilst active | + | WindowClosing | WindowClosing | Window closing | + | WindowDidMove | WindowDidMove | Window moved | + | WindowDidResize | WindowDidResize | Window resized | + | WindowEndMove | - | Window finished moving | + | WindowEndResize | - | Window finished resising | + | WindowFullscreen | WindowFullscreen | Window entered fullscreen | + | WindowHide | WindowHide | Window hidden | + | WindowInactive | - | Window became inactive | + | WindowKillFocus | WindowLostFocus | Window lost focus | + | WindowMaximise | WindowMaximise | Window maximised | + | WindowMinimise | WindowMinimise | Window minimised | + | WindowPaint | - | Window needs painting | + | WindowRestore | WindowRestore | Window restored | + | WindowSetFocus | WindowFocus | Window gained focus | + | WindowShow | WindowShow | Window shown | + | WindowStartMove | - | Window started moving | + | WindowStartResize | - | Window started resising | + | WindowUnFullscreen | WindowUnFullscreen | Window exited fullscreen | + | WindowUnMaximise | WindowUnMaximise | Window unmaximised | + | WindowUnMinimise | WindowUnMinimise | Window unminimised | + | WindowZOrderChanged | - | Window z-order changed | + + #### Input Events + | Event Name | Description | + |------------|-------------| + | WindowDragDrop | Files dragged and dropped | + | WindowDragEnter | Drag entered window | + | WindowDragLeave | Drag left window | + | WindowDragOver | Drag over window | + | WindowKeyDown | Key pressed | + | WindowKeyUp | Key released | + | WindowNonClientHit | Mouse hit in non-client area | + | WindowNonClientMouseDown | Mouse down in non-client area | + | WindowNonClientMouseLeave | Mouse left non-client area | + | WindowNonClientMouseMove | Mouse move in non-client area | + | WindowNonClientMouseUp | Mouse up in non-client area | + + + + | Event Name | Common Event | Description | + |------------|--------------|-------------| + | WindowDeleteEvent | WindowClosing | Window delete requested | + | WindowDidMove | WindowDidMove | Window moved | + | WindowDidResize | WindowDidResize | Window resized | + | WindowFocusIn | WindowFocus | Window gained focus | + | WindowFocusOut | WindowLostFocus | Window lost focus | + | WindowLoadChanged | WindowShow | Window load state changed | + + + +## Menu Events + +Menu events are triggered by menu-specific actions such as opening, closing, and interacting with menu items. These events are useful for creating dynamic menus and responding to menu interactions. + +```go +// Listen for menu events +app.OnApplicationEvent(events.Mac.MenuDidOpen, func(event *application.ApplicationEvent) { + app.Logger.Info("Menu opened!") +}) + +app.OnApplicationEvent(events.Mac.MenuWillSendAction, func(event *application.ApplicationEvent) { + app.Logger.Info("Menu about to send action!") +}) +``` + +For more information about menus, see the [Application Menu](/learn/application-menu) and [Context Menu](/learn/context-menu) documentation. + +### Platform-Specific Menu Events + + + + | Event Name | Description | + |------------|-------------| + | MenuDidAddItem | Menu item added | + | MenuDidBeginTracking | Menu began tracking | + | MenuDidClose | Menu closed | + | MenuDidDisplayItem | Menu item displayed | + | MenuDidEndTracking | Menu ended tracking | + | MenuDidHighlightItem | Menu item highlighted | + | MenuDidOpen | Menu opened | + | MenuDidPopUp | Menu popped up | + | MenuDidRemoveItem | Menu item removed | + | MenuDidSendAction | Menu sent action | + | MenuDidSendActionToItem | Menu sent action to item | + | MenuDidUpdate | Menu updated | + | MenuWillAddItem | Menu will add item | + | MenuWillBeginTracking | Menu will begin tracking | + | MenuWillDisplayItem | Menu will display item | + | MenuWillEndTracking | Menu will end tracking | + | MenuWillHighlightItem | Menu will highlight item | + | MenuWillOpen | Menu will open | + | MenuWillPopUp | Menu will pop up | + | MenuWillRemoveItem | Menu will remove item | + | MenuWillSendAction | Menu will send action | + | MenuWillSendActionToItem | Menu will send action to item | + | MenuWillUpdate | Menu will update | + + + + Windows does not currently provide specific menu events. + + + + Linux does not currently provide specific menu events. + + + +## WebView Events + +WebView events are triggered by navigation and loading state changes in the WebView component. These events are useful for tracking page loads and navigation state. + +```go +// Listen for WebView navigation events +app.OnApplicationEvent(events.Mac.WebViewDidStartProvisionalNavigation, func(event *application.ApplicationEvent) { + app.Logger.Info("WebView started loading a page!") +}) + +app.OnApplicationEvent(events.Mac.WebViewDidFinishNavigation, func(event *application.ApplicationEvent) { + app.Logger.Info("WebView finished loading the page!") +}) + +// On Windows +app.OnApplicationEvent(events.Windows.WebViewNavigationCompleted, func(event *application.ApplicationEvent) { + app.Logger.Info("WebView navigation completed!") +}) +``` + +### Platform-Specific WebView Events + + + + | Event Name | Description | + |------------|-------------| + | WebViewDidCommitNavigation | Navigation committed | + | WebViewDidFinishNavigation | Navigation finished | + | WebViewDidReceiveServerRedirectForProvisionalNavigation | Server redirect received | + | WebViewDidStartProvisionalNavigation | Provisional navigation started | + + + + | Event Name | Description | + |------------|-------------| + | WebViewNavigationCompleted | Navigation completed | + + + + Linux does not currently provide specific WebView events. + + + +## Custom Events + +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: + + + +```go +// NEW: Using the Event Manager (recommended) +app.Event.Emit("myevent", "hello") + +// Emit from a specific window +window.EmitEvent("windowevent", "window specific data") +``` + + +```go +// Traditional API (still supported) +app.EmitEvent("myevent", "hello") + +// Emit from a specific window +window.EmitEvent("windowevent", "window specific data") +``` + + + +### Handling Custom Events + +Listen for custom events using the event management methods: + + + +```go +// 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) +``` + + +```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) +``` + + + +## Event Cancellation + +Events can be cancelled to prevent their default behaviour or stop propagation to other listeners. This is particularly useful for hooks that need to control window closing, menu actions, or other system events. + +### Cancelling Events + +To cancel an event, call the `Cancel()` method on the event object: + +```go +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Prevent the window from closing + e.Cancel() +}) +``` + +### Checking Event Cancellation + +You can check if an event has been cancelled using the `IsCancelled()` method: + +```go +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if e.IsCancelled() { + app.Logger.Info("Window closing was cancelled by another hook") + return + } + // Process event +}) + +// For custom events +app.Event.On("myevent", func(e *application.CustomEvent) { + if e.IsCancelled() { + app.Logger.Info("Event was cancelled") + return + } + // Process event +}) +``` + +:::tip[Pro Tip] +Remember that event cancellation in hooks affects all subsequent hooks and listeners, whilst cancellation in standard listeners only affects listeners that haven't yet been called. +::: \ No newline at end of file diff --git a/docs/src/content/docs/learn/keybindings.mdx b/docs/src/content/docs/learn/keybindings.mdx new file mode 100644 index 000000000..0998f096e --- /dev/null +++ b/docs/src/content/docs/learn/keybindings.mdx @@ -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 + + + + + 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 + ``` + + + + + + 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 + ``` + + + + + + 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 + ``` + + + + +## 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. +::: \ No newline at end of file diff --git a/docs/src/content/docs/learn/manager-api.mdx b/docs/src/content/docs/learn/manager-api.mdx new file mode 100644 index 000000000..d58445742 --- /dev/null +++ b/docs/src/content/docs/learn/manager-api.mdx @@ -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() +``` \ No newline at end of file diff --git a/docs/src/content/docs/learn/menu-reference.mdx b/docs/src/content/docs/learn/menu-reference.mdx new file mode 100644 index 000000000..b0659f061 --- /dev/null +++ b/docs/src/content/docs/learn/menu-reference.mdx @@ -0,0 +1,188 @@ +--- +title: Menu Reference +sidebar: + order: 52 +--- + +This reference document covers the common menu item types and properties available in Wails v3. These features are shared between application menus and context menus. + +## Menu Item Types + +### Regular Menu Items + +The most basic type of menu item displays text and triggers an action when clicked: + +```go +menuItem := menu.Add("Click Me") +menuItem.OnClick(func(ctx *application.Context) { + // Handle click +}) +``` + +### Checkboxes + +Checkbox menu items provide a toggleable state: + +```go +checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked +checkbox.OnClick(func(ctx *application.Context) { + isChecked := ctx.ClickedMenuItem().Checked() + // Handle state change +}) +``` + +### Radio Groups + +Radio items create mutually exclusive options. Items are grouped automatically when placed adjacently: + +```go +menu.AddRadio("Option 1", true) // true = initially selected +menu.AddRadio("Option 2", false) +menu.AddRadio("Option 3", false) +``` + +### Submenus + +Submenus allow you to create nested menu structures: + +```go +submenu := menu.AddSubmenu("More Options") +submenu.Add("Submenu Item 1") +submenu.Add("Submenu Item 2") +``` + +### Separators + +Separators add visual dividers between menu items: + +```go +menu.Add("Item 1") +menu.AddSeparator() +menu.Add("Item 2") +``` + +## Menu Item Properties + +### Label + +The text displayed for the menu item: + +```go +menuItem := menu.Add("Initial Label") +menuItem.SetLabel("New Label") +``` + +### Enabled State + +Control whether the menu item can be interacted with: + +```go +menuItem := menu.Add("Disabled Item") +menuItem.SetEnabled(false) +``` + +### Checked State + +For checkbox and radio items, control or query their checked state: + +```go +checkbox := menu.AddCheckbox("Feature", false) +checkbox.SetChecked(true) +isChecked := checkbox.Checked() +``` + +### Accelerators + +Add keyboard shortcuts to menu items: + +```go +menuItem := menu.Add("Save") +menuItem.SetAccelerator("CmdOrCtrl+S") +``` + +Common accelerator modifiers: +- `CmdOrCtrl`: Command on macOS, Control on Windows/Linux +- `Shift` +- `Alt`: Option on macOS +- `Ctrl`: Control key on all platforms + +## Event Handling + +### Click Events + +Handle menu item clicks using the `OnClick` method: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Access the clicked item + clickedItem := ctx.ClickedMenuItem() + + // Get current state + label := clickedItem.Label() + isChecked := clickedItem.Checked() + + // Update the item + clickedItem.SetLabel("New Label") +}) +``` + +### Shared Event Handlers + +Event handlers can be shared amongst multiple menu items: + +```go +handleClick := func(ctx *application.Context) { + item := ctx.ClickedMenuItem() + // Common handling logic +} + +menu.Add("Item 1").OnClick(handleClick) +menu.Add("Item 2").OnClick(handleClick) +``` + +## Dynamic Updates + +Menu items can be updated dynamically during runtime: + +```go +menuItem := menu.Add("Initial State") + +// Later, update the item +menuItem.SetLabel("New Label") +menuItem.SetEnabled(false) +menuItem.SetChecked(true) + +// Apply changes +menu.Update() +``` + +:::note[Update Required] +After modifying menu items, call `Update()` on the parent menu to apply the changes. +::: + +## Best Practices + +1. Use clear, concise labels that describe the action +2. Group related items together using separators +3. Limit submenu depth to maintain usability +4. Provide keyboard shortcuts for common actions +5. Keep radio groups focused on a single choice +6. Update menu items to reflect application state +7. Handle all possible states in click handlers + +:::tip[Pro Tip] +When sharing event handlers, use the `ctx.ClickedMenuItem()` method to determine which item triggered the event and handle it accordingly. +::: + +## Platform Considerations + +:::note[Platform Behaviour] +Menu appearance and behaviour varies by platform: +- macOS: Uses native menu styling and supports system roles +- Windows: Follows Windows menu conventions +- Linux: Adapts to the desktop environment's theme +::: + +:::danger[Warning] +Always test menu functionality across all supported platforms, as behaviour and appearance may vary significantly. +::: diff --git a/docs/src/content/docs/learn/notifications.mdx b/docs/src/content/docs/learn/notifications.mdx new file mode 100644 index 000000000..8cb63646b --- /dev/null +++ b/docs/src/content/docs/learn/notifications.mdx @@ -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 + + + + + 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 + + + + + + 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` + + + + + + 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 + + + + +## 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 +} +``` \ No newline at end of file diff --git a/docs/src/content/docs/learn/runtime.mdx b/docs/src/content/docs/learn/runtime.mdx new file mode 100644 index 000000000..7b66a06b3 --- /dev/null +++ b/docs/src/content/docs/learn/runtime.mdx @@ -0,0 +1,91 @@ +--- +title: Runtime +sidebar: + order: 30 +--- + +The Wails runtime is the standard library for Wails applications. It provides a +number of features that may be used in your applications, including: + +- Window management +- Dialogs +- Browser integration +- Clipboard +- Menus +- System information +- Events +- Context Menus +- Screens +- WML (Wails Markup Language) + +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 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 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. + +The package is available on npm and can be installed using: + +```shell +npm install --save @wailsio/runtime +``` + +## Using a pre-built bundle + +Some projects will not use a Javascript bundler and may prefer to use a +pre-built bundled version of the runtime. This version can be generated locally +using the following command: + +```shell +wails3 generate runtime +``` + +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 + + + + + + + +``` + +:::caution +It is important to include the `type="module"` attribute on the ` + + + ``` + + Run `wails3 dev` to start the dev server. After a few seconds, the application should open. + + Type in some text and click the "Generate QR Code" button. You should see a QR code in the center of the page: + + QR Code + +
+
+ +7. ## Alternative Approach + + So far, we have covered the following areas: + - Creating a new Service + - Generating Bindings + - Using the Bindings in our Frontend code + + If the aim of your service is to serve files/assets/media to the frontend, like a traditional web server, + then there is an alternative approach to achieve the same result. + + 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={4-5,37-65} + package main + + import ( + "net/http" + "strconv" + + "github.com/skip2/go-qrcode" + ) + + // QRService handles QR code generation + type QRService struct { + // We can add state here if needed + } + + // NewQRService creates a new QR service + func NewQRService() *QRService { + return &QRService{} + } + + // 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 + } + + 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.go` to specify the route that the QR code service should be accessible on: + + ```go title="main.go" ins={8-10} + func main() { + + app := application.New(application.Options{ + Name: "myproject", + Description: "A demo of using raw HTML & CSS", + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewService(NewQRService(), application.ServiceOptions{ + Route: "/qrservice", + }), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "myproject", + Width: 600, + Height: 400, + }) + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } + } + ``` + + :::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" + async function generateQR() { + const text = document.getElementById('text').value; + if (!text) { + alert('Please enter some text'); + return; + } + + const img = document.getElementById('qrcode'); + // Make the image source the path to the QR code service, passing the text + img.src = `/qrservice?text=${encodeURIComponent(text)}` + } + + export function initializeQRGenerator() { + const button = document.getElementById('generateButton'); + if (button) { + button.addEventListener('click', generateQR); + } else { + console.error('Generate button not found'); + } + } + ``` + + Running the application again should result in the same QR code: + + QR Code +
+ +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. + ::: +
+ + diff --git a/docs/src/content/docs/whats-new.md b/docs/src/content/docs/whats-new.md new file mode 100644 index 000000000..8fd2f9c6a --- /dev/null +++ b/docs/src/content/docs/whats-new.md @@ -0,0 +1,407 @@ +--- +title: What's New in Wails v3 +--- + +Wails v3 introduces significant changes from v2. It replaces the +single-window, declarative API with a more flexible procedural approach. This +new API design improves code readability and simplifies development, especially +for complex multi-window applications. + +Wails v3 represents a substantial evolution in how desktop applications +can be built using Go and web technologies. + +## Multiple Windows + +Wails v3 introduces the ability to create and manage multiple windows within a +single application. This feature allows developers to design more complex and +versatile user interfaces, moving beyond the limitations of single-window +applications. + +Each window can be independently configured, providing flexibility in terms of +size, position, content, and behavior. This enables the creation of applications +with separate windows for different functionalities, such as main interfaces, +settings panels, or auxiliary views. + +Developers can create, manipulate, and manage these windows programmatically, +allowing for dynamic user interfaces that adapt to user needs and application +states. + +:::tip[Multiple Windows] +
Example + +```go +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Multi Window Demo", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + window1 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + }) + + window2 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Window 2", + }) + + // load the embedded html from the embed.FS + window1.SetURL("/") + window1.Center() + + // Load an external URL + window2.SetURL("https://wails.app") + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} +``` +
+ +::: + +## System Tray Integration + +Wails v3 introduces robust support for system tray functionality, allowing your +application to maintain a persistent presence on the user's desktop. This +feature is particularly useful for applications that need to run in the +background or provide quick access to key functions. + +Key features of the Wails v3 system tray integration include: + +1. Window Attachment: You can associate a window with the system tray icon. When + activated, this window will be centered relative to the icon's position, + providing a great way to quickly access your application. + +2. Comprehensive Menu Support: Create rich, interactive menus that users can + access directly from the system tray icon. This allows for quick actions + without needing to open the full application window. + +3. Adaptive Icon Display: Support for both light and dark mode icons ensures + your application's system tray icon remains visible and aesthetically + pleasing across different system themes. Template icons are also supported on macOS. + +:::tip[Systray] + +
Example + + +```go +package main + +import ( + _ "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 800, + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + + systemTray := app.NewSystemTray() + + // Support for template icons on macOS + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } else { + // Support for light/dark mode icons + systemTray.SetDarkModeIcon(icons.SystrayDark) + systemTray.SetIcon(icons.SystrayLight) + } + + // Support for menu + myMenu := app.NewMenu() + myMenu.Add("Hello World!").OnClick(func(_ *application.Context) { + println("Hello World!") + }) + systemTray.SetMenu(myMenu) + + // This will center the window to the systray icon with a 5px offset + // It will automatically be shown when the systray icon is clicked + // and hidden when the window loses focus + systemTray.AttachWindow(window).WindowOffset(5) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} +``` +
+::: + +## Improved bindings generation + +Wails v3 introduces a significant improvement in how bindings are generated for +your project. Bindings are the glue that connects your Go backend to your +frontend, allowing seamless communication between the two. + +Binding generation is now done using a sophisticated static analyzer that +radically improves the binding generation process. It offers enhanced speed and +preserves code quality by maintaining comments and parameter names. + +The binding generation process has been simplified, requiring only a single +command: `wails3 generate bindings`. + +:::tip[Bindings] + +
Example + +```js +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import { main } from "./models"; + +window.go = window.go || {}; +window.go.main = { + GreetService: { + /** + * GreetService.Greet + * Greet greets a person + * @param name {string} + * @returns {Promise} + **/ + Greet: function (name) { + wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); + }, + + /** + * GreetService.GreetPerson + * GreetPerson greets a person + * @param person {main.Person} + * @returns {Promise} + **/ + GreetPerson: function (person) { + wails.CallByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); + }, + }, +}; +``` + +
+ +::: + +## Improved build system + +Wails v3 introduces a more flexible and transparent build system, addressing the +limitations of its predecessor. In v2, the build process was largely opaque and +difficult to customise, which could be frustrating for developers seeking more +control over their project's build process. + +All the heavy lifting that the v2 build system did, such as icon generation and +manifest creation, have been added as tool commands in the CLI. We have +incorporated [Taskfile](https://taskfile.dev) into the CLI to orchestrate these +calls to bring the same developer experience as v2. However, this approach +brings the ultimate balance of flexibility and ease of use as you can now +customise the build process to your needs. + +You can even use make if that's your thing! + +:::tip[Taskfile.yml] + +
Example + +```yaml "Snippet from Taskfile.yml" +build:darwin: + summary: Builds the application + platforms: + - darwin + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" +``` +
+ +::: + +## Improved events + +Wails now emits events for various runtime operations and system activities. +This allows your application to respond to these events in real-time. +Additionally, cross-platform (common) events are available, enabling you to +write consistent event handling methods that work across different operating +systems. + +Event hooks can be registered to handle specific events synchronously. Unlike +the `On` method, these hooks allow you to cancel the event if needed. A common +use case is displaying a confirmation dialog before closing a window. This gives +you more control over the event flow and user experience. + +:::tip[Example of event handling] + +
Example + +```go +package main + +import ( + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Events Demo", + Description: "A demo of the Events API", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Custom event handling + app.Events.On("myevent", func(e *application.WailsEvent) { + log.Printf("[Go] WailsEvent received: %+v\n", e) + }) + + // OS specific application events + app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) { + println("events.Mac.ApplicationDidFinishLaunching fired!") + }) + + // Platform agnostic events + app.On(events.Common.ApplicationStarted, func(event *application.Event) { + println("events.Common.ApplicationStarted fired!") + }) + + win1 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "Takes 3 attempts to close me!", + }) + + var countdown = 3 + + // Register a hook to cancel the window closing + win1.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + countdown-- + if countdown == 0 { + println("Closing!") + return + } + println("Nope! Not closing!") + e.Cancel() + }) + + win1.On(events.Common.WindowFocus, func(e *application.WindowEvent) { + println("[Event] Window focus!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +
+ +::: + +## Wails Markup Language (wml) + +An experimental feature to call runtime methods using plain html, similar to +[htmx](https://htmx.org). + +:::tip[Example of wml] + +
Example + +```html + + + + + Wails ML Demo + + +

Wails ML Demo

+

This application contains no Javascript!

+ + + + + + + + + + +
+ Hover over me +
+ + +``` + +
+ +::: + +## Examples + +There are more examples available in the +[examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) +directory. Check them out! diff --git a/docs/src/env.d.ts b/docs/src/env.d.ts new file mode 100644 index 000000000..acef35f17 --- /dev/null +++ b/docs/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/docs/src/stylesheets/extra.css b/docs/src/stylesheets/extra.css new file mode 100644 index 000000000..47e0c5386 --- /dev/null +++ b/docs/src/stylesheets/extra.css @@ -0,0 +1,6 @@ +@import './mermaid.css'; + +html { + scrollbar-gutter: stable; + overflow-y: scroll; /* Show vertical scrollbar */ +} diff --git a/docs/src/stylesheets/mermaid.css b/docs/src/stylesheets/mermaid.css new file mode 100644 index 000000000..4818131fa --- /dev/null +++ b/docs/src/stylesheets/mermaid.css @@ -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%; + } +} diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 000000000..bcbf8b509 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} diff --git a/mkdocs-website/docs/en/changelog.md b/mkdocs-website/docs/en/changelog.md new file mode 100644 index 000000000..0023f38ca --- /dev/null +++ b/mkdocs-website/docs/en/changelog.md @@ -0,0 +1,89 @@ +# Changelog + + + +## [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 diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 000000000..215d80806 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,29 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-go:latest diff --git a/scripts/AUTOMATION-README.md b/scripts/AUTOMATION-README.md new file mode 100644 index 000000000..4096b1781 --- /dev/null +++ b/scripts/AUTOMATION-README.md @@ -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. diff --git a/scripts/issue-triage.ps1 b/scripts/issue-triage.ps1 new file mode 100644 index 000000000..6f6edd3ad --- /dev/null +++ b/scripts/issue-triage.ps1 @@ -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" diff --git a/scripts/issue-triage.sh b/scripts/issue-triage.sh new file mode 100644 index 000000000..5809b43a1 --- /dev/null +++ b/scripts/issue-triage.sh @@ -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!" diff --git a/scripts/pr-review-helper.ps1 b/scripts/pr-review-helper.ps1 new file mode 100644 index 000000000..75fae4c3b --- /dev/null +++ b/scripts/pr-review-helper.ps1 @@ -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" diff --git a/test-changelog-extraction.sh b/test-changelog-extraction.sh new file mode 100755 index 000000000..5fa8ae7f2 --- /dev/null +++ b/test-changelog-extraction.sh @@ -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!" \ No newline at end of file diff --git a/test-version-logic.sh b/test-version-logic.sh new file mode 100755 index 000000000..92abc50c3 --- /dev/null +++ b/test-version-logic.sh @@ -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!" \ No newline at end of file diff --git a/test-workflow.md b/test-workflow.md new file mode 100644 index 000000000..01421f9b1 --- /dev/null +++ b/test-workflow.md @@ -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 \ No newline at end of file diff --git a/v2/cmd/wails/doctor.go b/v2/cmd/wails/doctor.go index 5306cab17..015ef8a0b 100644 --- a/v2/cmd/wails/doctor.go +++ b/v2/cmd/wails/doctor.go @@ -67,7 +67,7 @@ func diagnoseEnvironment(f *flags.Doctor) error { wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()}) } - err = pterm.DefaultTable.WithData(wailsTableData).Render() + err = pterm.DefaultTable.WithBoxed().WithData(wailsTableData).Render() if err != nil { return err } diff --git a/v2/examples/customlayout/go.mod b/v2/examples/customlayout/go.mod index 005bb557d..ebd9bdbd0 100644 --- a/v2/examples/customlayout/go.mod +++ b/v2/examples/customlayout/go.mod @@ -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 => ../.. diff --git a/v2/examples/customlayout/go.sum b/v2/examples/customlayout/go.sum index 4ec20616f..6fd0e2a6b 100644 --- a/v2/examples/customlayout/go.sum +++ b/v2/examples/customlayout/go.sum @@ -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= diff --git a/v2/internal/app/app_devtools.go b/v2/internal/app/app_devtools.go index 60b221094..a84e8c283 100644 --- a/v2/internal/app/app_devtools.go +++ b/v2/internal/app/app_devtools.go @@ -1,8 +1,8 @@ -//go:build devtools - -package app - -// Note: devtools flag is also added in debug builds -func IsDevtoolsEnabled() bool { - return true -} +//go:build devtools + +package app + +// Note: devtools flag is also added in debug builds +func IsDevtoolsEnabled() bool { + return true +} diff --git a/v2/internal/frontend/desktop/darwin/WailsMenu.m b/v2/internal/frontend/desktop/darwin/WailsMenu.m index 66e5dd399..7e36da99a 100644 --- a/v2/internal/frontend/desktop/darwin/WailsMenu.m +++ b/v2/internal/frontend/desktop/darwin/WailsMenu.m @@ -184,16 +184,16 @@ return unicode(0x001b); } if( [key isEqualToString:@"left"] ) { - return unicode(0x001c); + return unicode(0xf702); } if( [key isEqualToString:@"right"] ) { - return unicode(0x001d); + return unicode(0xf703); } if( [key isEqualToString:@"up"] ) { - return unicode(0x001e); + return unicode(0xf700); } if( [key isEqualToString:@"down"] ) { - return unicode(0x001f); + return unicode(0xf701); } if( [key isEqualToString:@"space"] ) { return unicode(0x0020); diff --git a/v2/internal/frontend/desktop/windows/winc/w32/user32.go b/v2/internal/frontend/desktop/windows/winc/w32/user32.go index 8ca72ce4b..89ff985af 100644 --- a/v2/internal/frontend/desktop/windows/winc/w32/user32.go +++ b/v2/internal/frontend/desktop/windows/winc/w32/user32.go @@ -639,7 +639,7 @@ func GetSysColorBrush(nIndex int) HBRUSH { return HBRUSH(ret) */ - ret, _, _ := syscall.Syscall(getSysColorBrush, 1, + ret, _, _ := syscall.SyscallN(getSysColorBrush, uintptr(nIndex), 0, 0) diff --git a/v2/internal/go-common-file-dialog/cfd/iShellItem.go b/v2/internal/go-common-file-dialog/cfd/iShellItem.go index 6a747f4d9..c97efd8bb 100644 --- a/v2/internal/go-common-file-dialog/cfd/iShellItem.go +++ b/v2/internal/go-common-file-dialog/cfd/iShellItem.go @@ -40,8 +40,7 @@ func newIShellItem(path string) (*iShellItem, error) { func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) { var ptr *uint16 - ret, _, _ := syscall.Syscall(vtbl.GetDisplayName, - 2, + ret, _, _ := syscall.SyscallN(vtbl.GetDisplayName, uintptr(objPtr), 0x80058000, // SIGDN_FILESYSPATH uintptr(unsafe.Pointer(&ptr))) diff --git a/v2/internal/staticanalysis/test/standard/go.mod b/v2/internal/staticanalysis/test/standard/go.mod index 80e64f9cf..ae0c84abe 100644 --- a/v2/internal/staticanalysis/test/standard/go.mod +++ b/v2/internal/staticanalysis/test/standard/go.mod @@ -1,8 +1,8 @@ module changeme -go 1.18 +go 1.22 -require github.com/wailsapp/wails/v2 v2.3.1 +require github.com/wailsapp/wails/v2 v2.8.0 require ( github.com/bep/debounce v1.2.1 // indirect @@ -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 ) diff --git a/v2/internal/staticanalysis/test/standard/go.sum b/v2/internal/staticanalysis/test/standard/go.sum index 0517c2888..96e20126c 100644 --- a/v2/internal/staticanalysis/test/standard/go.sum +++ b/v2/internal/staticanalysis/test/standard/go.sum @@ -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= diff --git a/v2/pkg/assetserver/assethandler.go b/v2/pkg/assetserver/assethandler.go index b8e2df076..b56a5d033 100644 --- a/v2/pkg/assetserver/assethandler.go +++ b/v2/pkg/assetserver/assethandler.go @@ -21,9 +21,6 @@ type Logger interface { Error(message string, args ...interface{}) } -//go:embed defaultindex.html -var defaultHTML []byte - const ( indexHTML = "index.html" ) @@ -120,7 +117,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi if err != nil { return err } - defer file.Close() + defer func() { + _ = file.Close() + }() statInfo, err := file.Stat() if err != nil { @@ -143,7 +142,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi if err != nil { return err } - defer file.Close() + defer func() { + _ = file.Close() + }() statInfo, err = file.Stat() if err != nil { diff --git a/v2/pkg/options/linux/linux.go b/v2/pkg/options/linux/linux.go index 797450c27..1287f1da2 100644 --- a/v2/pkg/options/linux/linux.go +++ b/v2/pkg/options/linux/linux.go @@ -4,10 +4,10 @@ package linux type WebviewGpuPolicy int const ( - // WebviewGpuPolicyAlways Hardware acceleration is always enabled. - WebviewGpuPolicyAlways WebviewGpuPolicy = iota // WebviewGpuPolicyOnDemand Hardware acceleration is enabled/disabled as request by web contents. - WebviewGpuPolicyOnDemand + WebviewGpuPolicyOnDemand WebviewGpuPolicy = iota + // WebviewGpuPolicyAlways Hardware acceleration is always enabled. + WebviewGpuPolicyAlways // WebviewGpuPolicyNever Hardware acceleration is always disabled. WebviewGpuPolicyNever ) diff --git a/v3/.gitignore b/v3/.gitignore new file mode 100644 index 000000000..429ef3836 --- /dev/null +++ b/v3/.gitignore @@ -0,0 +1,11 @@ +examples/kitchensink/kitchensink +cmd/wails3/wails +/examples/systray-menu/systray +/examples/window/window +/examples/dialogs/dialogs +/examples/menu/menu +/examples/clipboard/clipboard +/examples/plain/plain +/cmd/wails3/ui/.task/ +!internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe +internal/commands/appimage_testfiles/appimage_testfiles \ No newline at end of file diff --git a/v3/.prettierignore b/v3/.prettierignore new file mode 100644 index 000000000..94c6af38e --- /dev/null +++ b/v3/.prettierignore @@ -0,0 +1 @@ +website \ No newline at end of file diff --git a/v3/.prettierrc.yml b/v3/.prettierrc.yml new file mode 100644 index 000000000..685d8b6e7 --- /dev/null +++ b/v3/.prettierrc.yml @@ -0,0 +1,6 @@ +overrides: + - files: + - "**/*.md" + options: + printWidth: 80 + proseWrap: always diff --git a/v3/README.md b/v3/README.md new file mode 100644 index 000000000..2d0c36a0b --- /dev/null +++ b/v3/README.md @@ -0,0 +1,9 @@ +# v3 Alpha + +Thanks for wanting to help out with testing/developing Wails v3! This guide will help you get started. + +## Getting Started + +All the instructions for getting started are in the v3 documentation directory: `mkdocs-website`. +Please read the README.md file in that directory for more information. + diff --git a/v3/TESTING.md b/v3/TESTING.md new file mode 100644 index 000000000..2e1486dc5 --- /dev/null +++ b/v3/TESTING.md @@ -0,0 +1,452 @@ +# Cross-Platform Testing Guide for Wails v3 + +This document describes the comprehensive cross-platform testing system for Wails v3 examples, supporting Mac, Linux, and Windows compilation. + +## Overview + +The testing system ensures all Wails v3 examples build successfully across all supported platforms: +- **macOS (Darwin)** - Native compilation +- **Windows** - Cross-compilation from any platform +- **Linux** - Multi-architecture Docker compilation (ARM64 + x86_64) + +## Test Directory Structure + +The testing infrastructure is organized in a dedicated test directory: + +```bash +v3/ +โ”œโ”€โ”€ test/ +โ”‚ โ””โ”€โ”€ docker/ +โ”‚ โ”œโ”€โ”€ Dockerfile.linux-arm64 # ARM64 native compilation +โ”‚ โ””โ”€โ”€ Dockerfile.linux-x86_64 # x86_64 native compilation +โ”œโ”€โ”€ Taskfile.yaml # Build task definitions +โ””โ”€โ”€ TESTING.md # This documentation +``` + +**Benefits of the organized structure:** +- **Separation of Concerns**: Testing files are isolated from application code +- **Clear Organization**: All Docker-related files in one location +- **Easier Maintenance**: Centralized testing infrastructure +- **Better Git Management**: Clean separation for .gitignore patterns + +## Available Commands + +### ๐Ÿš€ Complete Cross-Platform Testing +```bash +# Build all examples for ALL platforms (macOS + Windows + Linux) +task test:examples:all +``` +**Total: 129 builds** (43 examples ร— 3 platforms) + CLI code testing + +### All Examples (No DIR Parameter Needed) +```bash +# Current platform only (all 43 examples + CLI code) +task test:examples + +# All examples for specific Linux architectures +task test:examples:linux:docker # Auto-detect architecture +task test:examples:linux:docker:arm64 # ARM64 native +task test:examples:linux:docker:x86_64 # x86_64 native + +# CLI code testing only +task test:cli +``` + +### Single Example Builds (Requires DIR=example) +```bash +# macOS/Darwin single example +task test:example:darwin DIR=badge + +# Windows cross-compilation single example +task test:example:windows DIR=badge + +# Linux native builds (on Linux systems) +task test:example:linux DIR=badge + +# Linux Docker builds (multi-architecture) +task test:example:linux:docker DIR=badge # Auto-detect architecture +task test:example:linux:docker:arm64 DIR=badge # ARM64 native +task test:example:linux:docker:x86_64 DIR=badge # x86_64 native +``` + +## Build Artifacts + +All builds generate platform-specific binaries with clear naming: +- **macOS**: `testbuild-{example}-darwin` +- **Windows**: `testbuild-{example}-windows.exe` +- **Linux**: `testbuild-{example}-linux` +- **Linux ARM64**: `testbuild-{example}-linux-arm64` (Docker) +- **Linux x86_64**: `testbuild-{example}-linux-x86_64` (Docker) + +Example outputs: +```text +examples/badge/testbuild-badge-darwin +examples/badge/testbuild-badge-windows.exe +examples/badge/testbuild-badge-linux-arm64 +examples/badge/testbuild-badge-linux-x86_64 +``` + +## Validation Status + +### โœ… **Production Ready (v3.0.0-alpha)** +- **Total Examples**: 43 examples fully tested +- **macOS**: โœ… All examples compile successfully (100%) +- **Windows**: โœ… All examples cross-compile successfully (100%) +- **Linux**: โœ… Multi-architecture Docker compilation (ARM64 + x86_64) +- **Build System**: Comprehensive Taskfile.yaml integration +- **Git Integration**: Complete .gitignore patterns for build artifacts +- **Total Build Capacity**: 129 cross-platform builds per test cycle + +## Supported Examples + +The system builds all 43 Wails v3 examples: +- badge, badge-custom, binding, build +- cancel-async, cancel-chaining, clipboard, contextmenus +- dev, dialogs, dialogs-basic, drag-n-drop +- environment, events, events-bug, file-association +- frameless, gin-example, gin-routing, gin-service +- hide-window, html-dnd-api, ignore-mouse, keybindings +- menu, notifications, panic-handling, plain +- raw-message, screen, services, show-macos-toolbar +- single-instance, systray-basic, systray-custom, systray-menu +- video, window, window-api, window-call +- window-menu, wml + +**Recently Added (v3.0.0-alpha):** +- dev, events-bug, gin-example, gin-routing, gin-service +- html-dnd-api, notifications + +## Platform Requirements + +### macOS (Darwin) +- Go 1.23+ +- Xcode Command Line Tools +- No additional dependencies required + +**Environment Variables:** +```bash +CGO_LDFLAGS="-framework UniformTypeIdentifiers -mmacosx-version-min=10.13" +CGO_CFLAGS="-mmacosx-version-min=10.13" +``` + +### Windows (Cross-compilation) +- Go 1.23+ +- No additional dependencies for cross-compilation + +**Environment Variables:** +```bash +GOOS=windows +GOARCH=amd64 +``` + +### Linux (Docker) - โœ… Multi-Architecture Support +Uses Ubuntu 24.04 base image with full GTK development environment: + +**Current Status:** Complete multi-architecture Docker compilation system +- โœ… ARM64 native compilation (Ubuntu 24.04) +- โœ… x86_64 native compilation (Ubuntu 24.04) +- โœ… Automatic architecture detection +- โœ… All dependencies install correctly (GTK + WebKit) +- โœ… Go 1.24 environment configured for each architecture +- โœ… Native compilation eliminates cross-compilation CGO issues + +**Architecture Support:** +- **ARM64**: Native compilation using `Dockerfile.linux-arm64` +- **x86_64**: Native compilation using `Dockerfile.linux-x86_64` with `--platform=linux/amd64` +- **Auto-detect**: Taskfile automatically selects appropriate architecture + +**Core Dependencies:** +- `build-essential` - GCC compiler toolchain (architecture-specific) +- `pkg-config` - Package configuration tool +- `libgtk-3-dev` - GTK+ 3.x development files +- `libwebkit2gtk-4.1-dev` - WebKit2GTK development files +- `git` - Version control (for go mod operations) +- `ca-certificates` - HTTPS support + +**Docker Images:** +- `wails-v3-linux-arm64` - Ubuntu 24.04 ARM64 native compilation (built from `test/docker/Dockerfile.linux-arm64`) +- `wails-v3-linux-x86_64` - Ubuntu 24.04 x86_64 native compilation (built from `test/docker/Dockerfile.linux-x86_64`) +- `wails-v3-linux-fixed` - Legacy unified image (deprecated) + +## Docker Configuration + +### Multi-Architecture Build System + +#### ARM64 Native Build Environment (`test/docker/Dockerfile.linux-arm64`) +```dockerfile +FROM ubuntu:24.04 +# ARM64 native compilation environment +# Go 1.24.0 ARM64 binary (go1.24.0.linux-arm64.tar.gz) +# Native GCC toolchain for ARM64 +# All GTK/WebKit dependencies for ARM64 +# Build script: /build/build-linux-arm64.sh +# Output: testbuild-{example}-linux-arm64 +``` + +#### x86_64 Native Build Environment (`test/docker/Dockerfile.linux-x86_64`) +```dockerfile +FROM --platform=linux/amd64 ubuntu:24.04 +# x86_64 native compilation environment +# Go 1.24.0 x86_64 binary (go1.24.0.linux-amd64.tar.gz) +# Native GCC toolchain for x86_64 +# All GTK/WebKit dependencies for x86_64 +# Build script: /build/build-linux-x86_64.sh +# Output: testbuild-{example}-linux-x86_64 +``` + +### Available Docker Tasks + +#### Architecture-Specific Tasks +```bash +# ARM64 builds +task test:example:linux:docker:arm64 DIR=badge +task test:examples:linux:docker:arm64 + +# x86_64 builds +task test:example:linux:docker:x86_64 DIR=badge +task test:examples:linux:docker:x86_64 +``` + +#### Auto-Detection Tasks (Recommended) +```bash +# Single example (auto-detects host architecture) +task test:example:linux:docker DIR=badge + +# All examples (auto-detects host architecture) +task test:examples:linux:docker +``` + +## Implementation Details + +### Key Fixes Applied in v3.0.0-alpha + +#### 1. **Complete Example Coverage** +- **Before**: 35 examples tested +- **After**: 43 examples tested (100% coverage) +- **Added**: dev, events-bug, gin-example, gin-routing, gin-service, html-dnd-api, notifications + +#### 2. **Go Module Resolution** +- **Issue**: Inconsistent replace directives across examples +- **Fix**: Standardized all examples to use `replace github.com/wailsapp/wails/v3 => ../..` +- **Examples Fixed**: gin-example, gin-routing, notifications + +#### 3. **Frontend Asset Embedding** +- **Issue**: Some examples referenced missing `frontend/dist` directories +- **Fix**: Updated embed paths from `//go:embed all:frontend/dist` to `//go:embed all:frontend` +- **Examples Fixed**: file-association, notifications + +#### 4. **Manager API Migration** +- **Issue**: Windows badge service using deprecated API +- **Fix**: Updated `app.CurrentWindow()` โ†’ `app.Windows.Current()` +- **Files Fixed**: pkg/services/badge/badge_windows.go + +#### 5. **File Association Example** +- **Issue**: Undefined window variable +- **Fix**: Added proper window assignment from `app.Windows.NewWithOptions()` +- **Files Fixed**: examples/file-association/main.go + +### Build Performance +- **macOS**: ~2-3 minutes for all 43 examples +- **Windows Cross-Compile**: ~2-3 minutes for all 43 examples +- **Linux Docker**: ~5-10 minutes for all 43 examples (includes image build) +- **Total Build Time**: ~10-15 minutes for complete cross-platform validation (129 builds) + +## Usage Examples + +### Single Example Testing (Requires DIR Parameter) +```bash +# Test the badge example on all platforms +task test:example:darwin DIR=badge # macOS native +task test:example:windows DIR=badge # Windows cross-compile +task test:example:linux:docker DIR=badge # Linux Docker (auto-detect arch) +``` + +### All Examples Testing (No DIR Parameter) +```bash +# Test everything - all 43 examples, all platforms +task test:examples:all + +# This runs: +# 1. All Darwin builds (43 examples) +# 2. All Windows cross-compilation (43 examples) +# 3. All Linux Docker builds (43 examples, auto-architecture) + +# Platform-specific all examples +task test:examples # Current platform (43 examples) +task test:examples:linux:docker:arm64 # ARM64 builds (43 examples) +task test:examples:linux:docker:x86_64 # x86_64 builds (43 examples) +``` + +### Continuous Integration +```bash +# For CI/CD pipelines +task test:examples:all # Complete cross-platform (129 builds) +task test:examples # Current platform only (43 builds) +``` + +## Build Process Details + +### macOS Builds +1. Sets macOS-specific CGO flags for compatibility +2. Runs `go mod tidy` in each example directory +3. Compiles with `go build -o testbuild-{example}-darwin` +4. Links against UniformTypeIdentifiers framework + +### Windows Cross-Compilation +1. Sets `GOOS=windows GOARCH=amd64` environment +2. Runs `go mod tidy` in each example directory +3. Cross-compiles with `go build -o testbuild-{example}-windows.exe` +4. No CGO dependencies required (uses Windows APIs) + +### Linux Docker Builds +1. **Auto-Detection**: Detects host architecture (ARM64 or x86_64) +2. **Image Selection**: Uses appropriate Ubuntu 24.04 image for target architecture +3. **Native Compilation**: Eliminates cross-compilation CGO issues +4. **Environment Setup**: Full GTK/WebKit development environment +5. **Build Process**: Runs `go mod tidy && go build` with native toolchain +6. **Output**: Architecture-specific binaries (`-linux-arm64` or `-linux-x86_64`) + +## Troubleshooting + +### Common Issues (All Resolved in v3.0.0-alpha) + +#### **Go Module Resolution Errors** +```bash +Error: replacement directory ../wails/v3 does not exist +``` +**Solution**: All examples now use standardized `replace github.com/wailsapp/wails/v3 => ../..` + +#### **Frontend Asset Embedding Errors** +```bash +Error: pattern frontend/dist: no matching files found +``` +**Solution**: Updated to `//go:embed all:frontend` for examples without dist directories + +#### **Manager API Errors** +```bash +Error: app.CurrentWindow undefined +``` +**Solution**: Updated to use new manager pattern `app.Windows.Current()` + +#### **Build Warnings** +Some examples may show compatibility warnings (e.g., notifications using macOS 10.14+ APIs with 10.13 target). These are non-blocking warnings that can be addressed separately. + +### Performance Optimization + +#### **Parallel Builds** +```bash +# The task system automatically runs builds in parallel where possible +task v3:test:examples:all # Optimized for maximum throughput +``` + +#### **Selective Testing** +```bash +# Test specific examples to debug issues +task v3:test:example:darwin DIR=badge +task v3:test:example:windows DIR=contextmenus +``` + +### Performance Tips + +**Parallel Builds:** +```bash +# Build multiple examples simultaneously +task v3:test:example:darwin DIR=badge & +task v3:test:example:darwin DIR=binding & +task v3:test:example:darwin DIR=build & +wait +``` + +**Docker Image Caching:** +```bash +# Pre-build Docker images +docker build -f Dockerfile.linux -t wails-v3-linux-builder . +docker build -f Dockerfile.linux-simple -t wails-v3-linux-simple . +``` + +## Integration with Git + +### Ignored Files +All build artifacts are automatically ignored via `.gitignore`: +```gitignore +/v3/examples/*/testbuild-* +``` + +### Clean Build Environment +```bash +# Remove all test build artifacts +find v3/examples -name "testbuild-*" -delete +``` + +## Validation Results + +### Current Status (as of implementation): +- โœ… **macOS**: All 43 examples compile successfully +- โœ… **Windows**: All 43 examples cross-compile successfully +- โœ… **Linux**: Multi-architecture Docker system fully functional + +### Build Time Estimates: +- **macOS**: ~2-3 minutes for all examples +- **Windows**: ~2-3 minutes for all examples (cross-compile) +- **Linux Docker**: ~5-10 minutes for all examples (includes image build and compilation) +- **Complete Cross-Platform**: ~10-15 minutes for 129 total builds + +## Future Enhancements + +### Planned Improvements: +1. **Automated Testing**: Add runtime testing in addition to compilation +2. **Multi-Architecture**: Support ARM64 builds for Apple Silicon and Windows ARM +3. **Build Caching**: Implement Go build cache for faster repeated builds +4. **Parallel Docker**: Multi-stage Docker builds for faster Linux compilation +5. **Platform Matrix**: GitHub Actions integration for automated CI/CD + +### Platform Extensions: +- **FreeBSD**: Add BSD build support +- **Android/iOS**: Mobile platform compilation (when supported) +- **WebAssembly**: WASM target compilation + +## Changelog + +### v3.0.0-alpha (2025-06-20) +#### ๐ŸŽฏ Complete Cross-Platform Testing System + +#### **โœจ New Features** +- **Complete Example Coverage**: All 43 examples now tested (was 35) +- **Cross-Platform Validation**: Mac + Windows builds for all examples +- **Standardized Build Artifacts**: Consistent platform-specific naming +- **Enhanced Git Integration**: Complete .gitignore patterns for build artifacts + +#### **๐Ÿ› Major Fixes** +- **Go Module Resolution**: Standardized replace directives across all examples +- **Frontend Asset Embedding**: Fixed missing frontend/dist directory references +- **Manager API Migration**: Updated deprecated Windows badge service calls +- **File Association**: Fixed undefined window variable +- **Build Completeness**: Added 8 missing examples to test suite + +#### **๐Ÿ”ง Infrastructure Improvements** +- **Taskfile Integration**: Comprehensive cross-platform build tasks +- **Performance Optimization**: Parallel builds where possible +- **Error Handling**: Clear build failure reporting and debugging +- **Documentation**: Complete testing guide with troubleshooting + +#### **๐Ÿ“Š Validation Results** +- **macOS**: โœ… 43/43 examples compile successfully +- **Windows**: โœ… 43/43 examples cross-compile successfully +- **Build Time**: ~5-6 minutes for complete cross-platform validation +- **Reliability**: 100% success rate with proper error handling + +## Support + +For issues with cross-platform builds: +1. Check platform-specific requirements above +2. Review the troubleshooting section for resolved issues +3. Verify Go 1.24+ is installed +4. Check build logs for specific error messages +5. Use selective testing to isolate problems + +## References + +- [Wails v3 Documentation](https://wails.io/docs/) +- [Go Cross Compilation](https://golang.org/doc/install/cross) +- [GTK Development Libraries](https://www.gtk.org/docs/installations/linux) +- [Task Runner Documentation](https://taskfile.dev/) \ No newline at end of file diff --git a/v3/Taskfile.yaml b/v3/Taskfile.yaml new file mode 100644 index 000000000..42d064cae --- /dev/null +++ b/v3/Taskfile.yaml @@ -0,0 +1,368 @@ +# https://taskfile.dev + +version: "3" + +includes: + generator: + taskfile: ./internal/generator + dir: ./internal/generator + + runtime: + taskfile: ./internal/runtime + dir: ./internal/runtime + + website: + taskfile: ./website + dir: ./website + optional: true + + docs: + taskfile: ../docs + dir: ../docs + optional: true + +tasks: + recreate-template-dir: + dir: internal/templates + internal: true + silent: true + cmds: + - rm -rf {{.TEMPLATE_DIR}} + - mkdir -p {{.TEMPLATE_DIR}} + + install: + dir: cmd/wails3 + silent: true + cmds: + - go install + - echo "Installed wails CLI" + + release: + summary: Release a new version of Wails. Call with `task v3:release -- ` + dir: tasks/release + cmds: + - go run release.go {{.CLI_ARGS}} + + taskfile:upgrade: + cmds: + - go get -u github.com/go-task/task/v3 + + reinstall-cli: + dir: cmd/wails3 + internal: true + silent: true + cmds: + - go install + - echo "Reinstalled wails CLI" + + generate:events: + dir: tasks/events + cmds: + - go run generate.go + + precommit: + cmds: + - go test ./... + - task: format +# - task: docs:update:api + + test:example:darwin: + dir: 'examples/{{.DIR}}' + platforms: + - darwin + cmds: + - echo "Building example {{.DIR}} for Darwin" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-darwin{{exeExt}}" + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + CGO_CFLAGS: -mmacosx-version-min=10.13 + + test:example:windows: + dir: 'examples/{{.DIR}}' + platforms: + - windows + cmds: + - echo "Building example {{.DIR}} for Windows" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-windows.exe" + env: + GOOS: windows + GOARCH: amd64 + + test:example:linux: + dir: 'examples/{{.DIR}}' + platforms: + - linux + cmds: + - echo "Building example {{.DIR}} for Linux" + - go mod tidy + - go build -o "testbuild-{{.DIR}}-linux" + + test:example:linux:docker:arm64: + summary: Build a single example for Linux ARM64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building example {{.DIR}} for Linux ARM64 using Docker" + - docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 . + - docker run --rm wails-v3-linux-arm64 /build/build-linux-arm64.sh {{.DIR}} + + test:example:linux:docker:x86_64: + summary: Build a single example for Linux x86_64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building example {{.DIR}} for Linux x86_64 using Docker" + - docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 . + - docker run --rm wails-v3-linux-x86_64 /build/build-linux-x86_64.sh {{.DIR}} + + test:examples:linux:docker:arm64: + summary: Build all examples for Linux ARM64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building Docker image for Linux ARM64 compilation..." + - docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 . + - echo "Running Linux ARM64 compilation in Docker container..." + - docker run --rm wails-v3-linux-arm64 + + test:examples:linux:docker:x86_64: + summary: Build all examples for Linux x86_64 using Docker (Ubuntu 24.04) + cmds: + - echo "Building Docker image for Linux x86_64 compilation..." + - docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 . + - echo "Running Linux x86_64 compilation in Docker container..." + - docker run --rm wails-v3-linux-x86_64 + + test:example:linux:docker: + summary: Build a single example for Linux using Docker (auto-detect architecture) + cmds: + - echo "Auto-detecting architecture for Linux Docker build..." + - | + if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + echo "Detected ARM64, using ARM64 Docker image" + task test:example:linux:docker:arm64 DIR={{.DIR}} + else + echo "Detected x86_64, using x86_64 Docker image" + task test:example:linux:docker:x86_64 DIR={{.DIR}} + fi + + test:examples:linux:docker: + summary: Build all examples for Linux using Docker (auto-detect architecture) + cmds: + - echo "Auto-detecting architecture for Linux Docker build..." + - | + if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + echo "Detected ARM64, using ARM64 Docker image" + task test:examples:linux:docker:arm64 + else + echo "Detected x86_64, using x86_64 Docker image" + task test:examples:linux:docker:x86_64 + fi + + test:examples:all: + summary: Builds all examples for all platforms (Mac + Windows + Linux via Docker) + vars: + EXAMPLEDIRS: | + badge + badge-custom + binding + build + cancel-async + cancel-chaining + clipboard + contextmenus + dev + dialogs + dialogs-basic + drag-n-drop + environment + events + events-bug + file-association + frameless + gin-example + gin-routing + gin-service + hide-window + 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 "Building all examples for all platforms..." + - echo "=== Building for Darwin ===" + - for: { var: EXAMPLEDIRS } + task: test:example:darwin + vars: + DIR: "{{.ITEM}}" + - echo "=== Building for Windows (cross-compile) ===" + - for: { var: EXAMPLEDIRS } + task: test:example:windows + vars: + DIR: "{{.ITEM}}" + - echo "=== Building for Linux (Docker) ===" + - task: test:examples:linux:docker + - echo "=== Testing CLI Code ===" + - task: test:cli + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + + test:cli: + summary: Test CLI-related code compilation + cmds: + - echo "Testing CLI appimage testfiles compilation..." + - cd internal/commands/appimage_testfiles && go mod tidy && go build + - echo "โœ… CLI appimage testfiles compile successfully" + + test:cli:all: + summary: Test all CLI components and critical test files + cmds: + - echo "Testing CLI appimage testfiles..." + - cd internal/commands/appimage_testfiles && go mod tidy && go build + - echo "Testing window visibility test..." + - cd tests/window-visibility-test && go mod tidy && go build + - echo "Testing service implementations..." + - cd pkg/services/badge && go build + - echo "โœ… All CLI components compile successfully" + + test:generator: + summary: Test code generator test cases compilation + cmds: + - echo "Testing generator test cases (sample)..." + - cd internal/generator/testcases/function_single && go mod tidy && go build + - cd internal/generator/testcases/complex_method && go mod tidy && go build + - cd internal/generator/testcases/struct_literal_single && go mod tidy && go build + - echo "โœ… Generator test cases compile successfully" + + test:templates: + summary: Test template generation for core templates + cmds: + - echo "Testing template generation (core templates)..." + - task: install + - echo "Testing lit template generation..." + - rm -rf ./test-template-lit && wails3 init -n test-template-lit -t lit + - mkdir -p ./test-template-lit/frontend/dist && touch ./test-template-lit/frontend/dist/.keep + - cd ./test-template-lit && go mod tidy && go build + - rm -rf ./test-template-lit + - echo "Testing react template generation..." + - rm -rf ./test-template-react && wails3 init -n test-template-react -t react + - mkdir -p ./test-template-react/frontend/dist && touch ./test-template-react/frontend/dist/.keep + - cd ./test-template-react && go mod tidy && go build + - rm -rf ./test-template-react + - echo "โœ… Template generation tests completed successfully" + + test:infrastructure: + summary: Test critical infrastructure components + cmds: + - echo "=== Testing CLI Components ===" + - task: test:cli:all + - echo "=== Testing Generator ===" + - task: test:generator + - echo "=== Testing Templates ===" + - task: test:templates + - echo "=== Testing pkg/application ===" + - cd pkg/application && go test -c -o /dev/null ./... + - echo "=== Cleaning Up Test Binaries ===" + - task: clean:test:binaries + - echo "โœ… All infrastructure components test successfully" + + test:examples: + summary: Builds the examples for current platform only + vars: + EXAMPLEDIRS: | + badge + badge-custom + binding + build + cancel-async + cancel-chaining + clipboard + contextmenus + dev + dialogs + dialogs-basic + drag-n-drop + environment + events + events-bug + file-association + frameless + gin-example + gin-routing + gin-service + hide-window + 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" diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md new file mode 100644 index 000000000..8e4648038 --- /dev/null +++ b/v3/UNRELEASED_CHANGELOG.md @@ -0,0 +1,53 @@ +# Unreleased Changes + + + +## Added + + +## Changed + + +## Fixed + + +## Deprecated + + +## Removed + + +## Security + + +--- + +### Example Entries: + +**Added:** +- Add support for custom window icons in application options +- Add new `SetWindowIcon()` method to runtime API (#1234) + +**Changed:** +- Update minimum Go version requirement to 1.21 +- Improve error messages for invalid configuration files + +**Fixed:** +- Fix memory leak in event system during window close operations (#5678) +- Fix crash when using context menus on Linux with Wayland + +**Security:** +- Update dependencies to address CVE-2024-12345 in third-party library diff --git a/v3/cmd/wails3/README.md b/v3/cmd/wails3/README.md new file mode 100644 index 000000000..8924153dd --- /dev/null +++ b/v3/cmd/wails3/README.md @@ -0,0 +1,83 @@ +# The Wails CLI + +The Wails CLI is a command line tool that allows you to create, build and run Wails applications. +There are a number of commands related to tooling, such as icon generation and asset bundling. + +## Commands + +### task + +The `task` command is for running tasks defined in `Taskfile.yml`. It is a wrapper around [Task](https://taskfile.dev). + +### generate + +The `generate` command is used to generate resources and assets for your Wails project. +It can be used to generate many things including: + - application icons, + - resource files for Windows applications + - Info.plist files for macOS deployments + +#### icon + +The `icon` command generates icons for your project. + +| Flag | Type | Description | Default | +|--------------------|--------|------------------------------------------------------|----------------------| +| `-example` | bool | Generates example icon file (appicon.png) | | +| `-input` | string | The input image file | | +| `-sizes` | string | The sizes to generate in .ico file (comma separated) | "256,128,64,48,32,16" | +| `-windowsFilename` | string | The output filename for the Windows icon | icon.ico | +| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns | + +```bash +wails3 generate icon -input myicon.png -sizes "32,64,128" -windowsFilename myicon.ico -macFilename myicon.icns +``` + +This will generate icons for mac and windows and save them in the current directory as `myicon.ico` +and `myicons.icns`. + +#### syso + +The `syso` command generates a Windows resource file (aka `.syso`). + +```bash +wails3 generate syso +``` + +| Flag | Type | Description | Default | +|-------------|--------|--------------------------------------------|------------------| +| `-example` | bool | Generates example manifest & info files | | +| `-manifest` | string | The manifest file | | +| `-info` | string | The info.json file | | +| `-icon` | string | The icon file | | +| `-out` | string | The output filename for the syso file | `wails.exe.syso` | +| `-arch` | string | The target architecture (amd64,arm64,386) | `runtime.GOOS` | + +If `-example` is provided, the command will generate example manifest and info files +in the current directory and exit. + +If `-manifest` is provided, the command will use the provided manifest file to generate +the syso file. + +If `-info` is provided, the command will use the provided info.json file to set the version +information in the syso file. + +NOTE: We use [winres](https://github.com/tc-hib/winres) to generate the syso file. Please +refer to the winres documentation for more information. + +NOTE: Whilst the tool will work for 32-bit Windows, it is not supported. Please use 64-bit. + +#### defaults + +```bash +wails3 generate defaults +``` +This will generate all the default assets and resources in the current directory. + +#### bindings + +```bash +wails3 generate bindings +``` + +Generates bindings and models for your bound Go methods and structs. \ No newline at end of file diff --git a/v3/cmd/wails3/main.go b/v3/cmd/wails3/main.go new file mode 100644 index 000000000..945a865df --- /dev/null +++ b/v3/cmd/wails3/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "os" + "runtime/debug" + + "github.com/pkg/browser" + + "github.com/pterm/pterm" + "github.com/samber/lo" + + "github.com/leaanthony/clir" + "github.com/wailsapp/wails/v3/internal/commands" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/term" +) + +func init() { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + commands.BuildSettings = lo.Associate(buildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + // Iterate over the Deps and add them to the build settings using a prefix of "mod." + for _, dep := range buildInfo.Deps { + commands.BuildSettings["mod."+dep.Path] = dep.Version + } +} + +func main() { + app := clir.NewCli("wails", "The Wails3 CLI", "v3") + app.NewSubCommand("docs", "Open the docs").Action(openDocs) + app.NewSubCommandFunction("init", "Initialise a new project", commands.Init) + app.NewSubCommandFunction("build", "Build the project", commands.Build) + app.NewSubCommandFunction("dev", "Run in Dev mode", commands.Dev) + app.NewSubCommandFunction("package", "Package application", commands.Package) + app.NewSubCommandFunction("doctor", "System status report", commands.Doctor) + app.NewSubCommandFunction("releasenotes", "Show release notes", commands.ReleaseNotes) + + task := app.NewSubCommand("task", "Run and list tasks") + var taskFlags commands.RunTaskOptions + task.AddFlags(&taskFlags) + task.Action(func() error { + return commands.RunTask(&taskFlags, task.OtherArgs()) + }) + task.LongDescription("\nUsage: wails3 task [taskname] [flags]\n\nTasks are defined in the `Taskfile.yaml` file. See https://taskfile.dev for more information.") + + generate := app.NewSubCommand("generate", "Generation tools") + generate.NewSubCommandFunction("build-assets", "Generate build assets", commands.GenerateBuildAssets) + generate.NewSubCommandFunction("icons", "Generate icons", commands.GenerateIcons) + generate.NewSubCommandFunction("syso", "Generate Windows .syso file", commands.GenerateSyso) + generate.NewSubCommandFunction("runtime", "Generate the pre-built version of the runtime", commands.GenerateRuntime) + generate.NewSubCommandFunction("webview2bootstrapper", "Generate WebView2 bootstrapper", commands.GenerateWebView2Bootstrapper) + generate.NewSubCommandFunction("template", "Generate a new template", commands.GenerateTemplate) + + update := app.NewSubCommand("update", "Update tools") + update.NewSubCommandFunction("build-assets", "Updates the build assets using the given config file", commands.UpdateBuildAssets) + update.NewSubCommandFunction("cli", "Updates the Wails CLI", commands.UpdateCLI) + + bindgen := generate.NewSubCommand("bindings", "Generate bindings + models") + var bindgenFlags flags.GenerateBindingsOptions + bindgen.AddFlags(&bindgenFlags) + bindgen.Action(func() error { + return commands.GenerateBindings(&bindgenFlags, bindgen.OtherArgs()) + }) + bindgen.LongDescription("\nUsage: wails3 generate bindings [flags] [patterns...]\n\nPatterns match packages to scan for bound types.\nPattern format is analogous to that of the Go build tool,\ne.g. './...' matches packages in the current directory and all descendants.\nIf no pattern is given, the tool will fall back to the current directory.") + generate.NewSubCommandFunction("constants", "Generate JS constants from Go", commands.GenerateConstants) + generate.NewSubCommandFunction(".desktop", "Generate .desktop file", commands.GenerateDotDesktop) + generate.NewSubCommandFunction("appimage", "Generate Linux AppImage", commands.GenerateAppImage) + + plugin := app.NewSubCommand("service", "Service tools") + plugin.NewSubCommandFunction("init", "Initialise a new service", commands.ServiceInit) + + tool := app.NewSubCommand("tool", "Various tools") + tool.NewSubCommandFunction("checkport", "Checks if a port is open. Useful for testing if vite is running.", commands.ToolCheckPort) + tool.NewSubCommandFunction("watcher", "Watches files and runs a command when they change", commands.Watcher) + tool.NewSubCommandFunction("cp", "Copy files", commands.Cp) + tool.NewSubCommandFunction("buildinfo", "Show Build Info", commands.BuildInfo) + tool.NewSubCommandFunction("package", "Generate Linux packages (deb, rpm, archlinux)", commands.ToolPackage) + tool.NewSubCommandFunction("version", "Bump semantic version", commands.ToolVersion) + + app.NewSubCommandFunction("version", "Print the version", commands.Version) + app.NewSubCommand("sponsor", "Sponsor the project").Action(openSponsor) + + defer printFooter() + + err := app.Run() + if err != nil { + pterm.Error.Println(err) + os.Exit(1) + } +} + +func printFooter() { + if !commands.DisableFooter { + docsLink := term.Hyperlink("https://v3.wails.io/getting-started/your-first-app/", "wails3 docs") + + pterm.Println(pterm.LightGreen("\nNeed documentation? Run: ") + pterm.LightBlue(docsLink)) + // Check if we're in a teminal + printer := pterm.PrefixPrinter{ + MessageStyle: pterm.NewStyle(pterm.FgLightGreen), + Prefix: pterm.Prefix{ + Style: pterm.NewStyle(pterm.FgRed, pterm.BgLightWhite), + Text: "โ™ฅ ", + }, + } + + linkText := term.Hyperlink("https://github.com/sponsors/leaanthony", "wails3 sponsor") + printer.Println("If Wails is useful to you or your company, please consider sponsoring the project: " + pterm.LightBlue(linkText)) + } +} + +func openDocs() error { + commands.DisableFooter = true + return browser.OpenURL("https://v3.wails.io/getting-started/your-first-app/") +} + +func openSponsor() error { + commands.DisableFooter = true + return browser.OpenURL("https://github.com/sponsors/leaanthony") +} diff --git a/v3/examples/README.md b/v3/examples/README.md new file mode 100644 index 000000000..753ec5138 --- /dev/null +++ b/v3/examples/README.md @@ -0,0 +1,17 @@ +# v3 + +*NOTE*: The examples in this directory may or may not compile / run at any given time during alpha development. + + +## Running the examples + + cd v3/examples/ + go mod tidy + go run . + +## Compiling the examples + + cd v3/examples/ + go mod tidy + go build + ./ diff --git a/v3/examples/badge-custom/README.md b/v3/examples/badge-custom/README.md new file mode 100644 index 000000000..ab4c5a3fb --- /dev/null +++ b/v3/examples/badge-custom/README.md @@ -0,0 +1,128 @@ +# Welcome to Your New Wails3 Project! +Now that you have your project set up, it's time to explore the custom badge features that Wails3 offers on **Windows**. + +## Exploring Custom Badge Features + +### Creating the Service with Custom Options (Windows Only) + +On Windows, you can customize the badge appearance with various options: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/badge" +import "image/color" + +// Create a badge service with custom options +options := badge.Options{ + TextColour: color.RGBA{255, 255, 255, 255}, // White text + BackgroundColour: color.RGBA{0, 0, 255, 255}, // Green background + FontName: "consolab.ttf", // Bold Consolas font + FontSize: 20, // Font size for single character + SmallFontSize: 14, // Font size for multiple characters +} + +badgeService := badge.NewWithOptions(options) + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(badgeService), + }, +}) +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon with the global options applied: + +#### Go +```go +// Set a default badge +badgeService.SetBadge("") + +// Set a numeric badge +badgeService.SetBadge("3") + +// Set a text badge +badgeService.SetBadge("New") +``` + +#### JS +```js +import {SetBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +// Set a default badge +SetBadge("") + +// Set a numeric badge +SetBadge("3") + +// Set a text badge +SetBadge("New") +``` + +### Setting a Custom Badge + +Set a badge on the application tile/dock icon with one-off options applied: + +#### Go +```go +// Set a default badge +badgeService.SetCustomBadge("") + +// Set a numeric badge +badgeService.SetCustomBadge("3") + +// Set a text badge +badgeService.SetCustomBadge("New") +``` + +#### JS +```js +import {SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +const options = { + BackgroundColour: RGBA.createFrom({ + R: 0, + G: 255, + B: 255, + A: 255, + }), + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: RGBA.createFrom({ + R: 0, + G: 0, + B: 0, + A: 255, + }), +} + +// Set a default badge +SetCustomBadge("", options) + +// Set a numeric badge +SetCustomBadge("3", options) + +// Set a text badge +SetCustomBadge("New", options) +``` + +### Removing a Badge + +Remove the badge from the application icon: + +#### Go +```go +badgeService.RemoveBadge() +``` + +#### JS +```js +import {RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +RemoveBadge() +``` \ No newline at end of file diff --git a/v3/examples/badge-custom/Taskfile.yml b/v3/examples/badge-custom/Taskfile.yml new file mode 100644 index 000000000..d1bcfaacf --- /dev/null +++ b/v3/examples/badge-custom/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "badge" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/badge-custom/build/Taskfile.yml b/v3/examples/badge-custom/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/badge-custom/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/badge-custom/build/appicon.png b/v3/examples/badge-custom/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/badge-custom/build/appicon.png differ diff --git a/v3/examples/badge-custom/build/config.yml b/v3/examples/badge-custom/build/config.yml new file mode 100644 index 000000000..aaa3240fb --- /dev/null +++ b/v3/examples/badge-custom/build/config.yml @@ -0,0 +1,63 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Info.dev.plist b/v3/examples/badge-custom/build/darwin/Info.dev.plist new file mode 100644 index 000000000..4caddf720 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + ยฉ now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Info.plist b/v3/examples/badge-custom/build/darwin/Info.plist new file mode 100644 index 000000000..0dc90b2e7 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + ยฉ now, My Company + + \ No newline at end of file diff --git a/v3/examples/badge-custom/build/darwin/Taskfile.yml b/v3/examples/badge-custom/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/badge-custom/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/badge-custom/build/darwin/icons.icns b/v3/examples/badge-custom/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/badge-custom/build/darwin/icons.icns differ diff --git a/v3/examples/badge-custom/build/linux/Taskfile.yml b/v3/examples/badge-custom/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/badge-custom/build/linux/appimage/build.sh b/v3/examples/badge-custom/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/badge-custom/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml b/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..5b7ea9d02 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "badge" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/badge" + dst: "/usr/local/bin/badge" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/badge.png" + - src: "./build/linux/badge.desktop" + dst: "/usr/share/applications/badge.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh b/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge-custom/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge-custom/build/windows/Taskfile.yml b/v3/examples/badge-custom/build/windows/Taskfile.yml new file mode 100644 index 000000000..534f4fb31 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/Taskfile.yml @@ -0,0 +1,63 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/badge-custom/build/windows/icon.ico b/v3/examples/badge-custom/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/badge-custom/build/windows/icon.ico differ diff --git a/v3/examples/badge-custom/build/windows/info.json b/v3/examples/badge-custom/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "ยฉ now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/badge-custom/build/windows/nsis/project.nsi b/v3/examples/badge-custom/build/windows/nsis/project.nsi new file mode 100644 index 000000000..985b8e207 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "badge" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "ยฉ now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh b/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..6fc10ab79 --- /dev/null +++ b/v3/examples/badge-custom/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "badge" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "ยฉ now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/badge-custom/build/windows/wails.exe.manifest b/v3/examples/badge-custom/build/windows/wails.exe.manifest new file mode 100644 index 000000000..1d8992a3d --- /dev/null +++ b/v3/examples/badge-custom/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/Inter Font License.txt b/v3/examples/badge-custom/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/badge-custom/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts new file mode 100644 index 000000000..f959d50dc --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the notifications service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * RemoveBadge removes the badge label from the application icon. + */ +export function RemoveBadge(): $CancellablePromise { + return $Call.ByID(2374916939); +} + +/** + * SetBadge sets the badge label on the application icon. + */ +export function SetBadge(label: string): $CancellablePromise { + return $Call.ByID(784276339, label); +} + +export function SetCustomBadge(label: string, options: $models.Options): $CancellablePromise { + return $Call.ByID(3058653106, label, options); +} diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts new file mode 100644 index 000000000..df3ea9723 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as BadgeService from "./badgeservice.js"; +export { + BadgeService +}; + +export { + Options +} from "./models.js"; diff --git a/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts new file mode 100644 index 000000000..67ed264c0 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts @@ -0,0 +1,58 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as color$0 from "../../../../../../../image/color/models.js"; + +export class Options { + "TextColour": color$0.RGBA; + "BackgroundColour": color$0.RGBA; + "FontName": string; + "FontSize": number; + "SmallFontSize": number; + + /** Creates a new Options instance. */ + constructor($$source: Partial = {}) { + if (!("TextColour" in $$source)) { + this["TextColour"] = (new color$0.RGBA()); + } + if (!("BackgroundColour" in $$source)) { + this["BackgroundColour"] = (new color$0.RGBA()); + } + if (!("FontName" in $$source)) { + this["FontName"] = ""; + } + if (!("FontSize" in $$source)) { + this["FontSize"] = 0; + } + if (!("SmallFontSize" in $$source)) { + this["SmallFontSize"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Options instance from a string or object. + */ + static createFrom($$source: any = {}): Options { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("TextColour" in $$parsedSource) { + $$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]); + } + if ("BackgroundColour" in $$parsedSource) { + $$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]); + } + return new Options($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = color$0.RGBA.createFrom; diff --git a/v3/examples/badge-custom/frontend/bindings/image/color/index.ts b/v3/examples/badge-custom/frontend/bindings/image/color/index.ts new file mode 100644 index 000000000..97b507b08 --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/image/color/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + RGBA +} from "./models.js"; diff --git a/v3/examples/badge-custom/frontend/bindings/image/color/models.ts b/v3/examples/badge-custom/frontend/bindings/image/color/models.ts new file mode 100644 index 000000000..0d4eab56d --- /dev/null +++ b/v3/examples/badge-custom/frontend/bindings/image/color/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 + * bits for each of red, green, blue and alpha. + * + * An alpha-premultiplied color component C has been scaled by alpha (A), so + * has valid values 0 <= C <= A. + */ +export class RGBA { + "R": number; + "G": number; + "B": number; + "A": number; + + /** Creates a new RGBA instance. */ + constructor($$source: Partial = {}) { + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source: any = {}): RGBA { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource as Partial); + } +} diff --git a/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf b/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge-custom/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/badge-custom/frontend/dist/assets/index--TgUfkZn.js b/v3/examples/badge-custom/frontend/dist/assets/index--TgUfkZn.js new file mode 100644 index 000000000..6d342144b --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/assets/index--TgUfkZn.js @@ -0,0 +1,6 @@ +var ue=Object.defineProperty;var de=(t,e,n)=>e in t?ue(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var j=(t,e,n)=>de(t,typeof e!="symbol"?e+"":e,n);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();const fe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function te(t=21){let e="",n=t|0;for(;n--;)e+=fe[Math.random()*64|0];return e}const we=window.location.origin+"/wails/runtime",z=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10});let pe=te();function R(t,e=""){return function(n,r=null){return me(t,n,e,r)}}async function me(t,e,n,r){var o,i;let s=new URL(we);s.searchParams.append("object",t.toString()),s.searchParams.append("method",e.toString()),r&&s.searchParams.append("args",JSON.stringify(r));let c={"x-wails-client-id":pe};n&&(c["x-wails-window-name"]=n);let l=await fetch(s,{headers:c});if(!l.ok)throw new Error(await l.text());return((i=(o=l.headers.get("Content-Type"))===null||o===void 0?void 0:o.indexOf("application/json"))!==null&&i!==void 0?i:-1)!==-1?l.json():l.text()}R(z.System);const P=function(){var t,e,n,r,o;try{if(!((e=(t=window.chrome)===null||t===void 0?void 0:t.webview)===null||e===void 0)&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if(!((o=(r=(n=window.webkit)===null||n===void 0?void 0:n.messageHandlers)===null||r===void 0?void 0:r.external)===null||o===void 0)&&o.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch{}return console.warn(` +%cโš ๏ธ Browser Environment Detected %c + +%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode. +More information at: https://v3.wails.io/learn/build/#using-a-browser-for-development +`,"background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;","background: transparent;","color: #ffffff; font-style: italic; font-weight: bold;"),null}();function k(t){P==null||P(t)}function ne(){return window._wails.environment.OS==="windows"}function he(){return!!window._wails.environment.Debug}function ge(){return new MouseEvent("mousedown").buttons===0}function re(t){var e;return t.target instanceof HTMLElement?t.target:!(t.target instanceof HTMLElement)&&t.target instanceof Node&&(e=t.target.parentElement)!==null&&e!==void 0?e:document.body}document.addEventListener("DOMContentLoaded",()=>{});window.addEventListener("contextmenu",Ee);const ye=R(z.ContextMenu),be=0;function ve(t,e,n,r){ye(be,{id:t,x:e,y:n,data:r})}function Ee(t){const e=re(t),n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(n){t.preventDefault();const r=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");ve(n,t.clientX,t.clientY,r)}else je(t,e)}function je(t,e){if(he())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return}if(e.isContentEditable)return;const n=window.getSelection(),r=n&&n.toString().length>0;if(r)for(let o=0;o{U=t,U||(v=E=!1,u())};window.addEventListener("mousedown",X,{capture:!0});window.addEventListener("mousemove",X,{capture:!0});window.addEventListener("mouseup",X,{capture:!0});for(const t of["click","contextmenu","dblclick"])window.addEventListener(t,Se,{capture:!0});function Se(t){(C||E)&&(t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault())}const F=0,Ce=1,I=2;function X(t){let e,n=t.buttons;switch(t.type){case"mousedown":e=F,D||(n=h|1<"u"||typeof e=="object"))try{var n=L.call(e);return(n===Te||n===Ae||n===Pe||n===Le)&&e("")==null}catch{}return!1})}function Ie(t){if(J(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;try{y(t,null,N)}catch(e){if(e!==O)return!1}return!Y(t)&&W(t)}function _e(t){if(J(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;if(He)return W(t);if(Y(t))return!1;var e=L.call(t);return e!==ke&&e!==Be&&!/^\[object HTML/.test(e)?!1:W(t)}const m=y?Ie:_e;var _;class G extends Error{constructor(e,n){super(e,n),this.name="CancelError"}}class x extends Error{constructor(e,n,r){super((r??"Unhandled rejection in cancelled promise.")+" Reason: "+Ue(n),{cause:n}),this.promise=e,this.name="CancelledRejectionError"}}const f=Symbol("barrier"),Q=Symbol("cancelImpl"),Z=(_=Symbol.species)!==null&&_!==void 0?_:Symbol("speciesPolyfill");class a extends Promise{constructor(e,n){let r,o;if(super((l,d)=>{r=l,o=d}),this.constructor[Z]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let i={promise:this,resolve:r,reject:o,get oncancelled(){return n??null},set oncancelled(l){n=l??void 0}};const s={get root(){return s},resolving:!1,settled:!1};Object.defineProperties(this,{[f]:{configurable:!1,enumerable:!1,writable:!0,value:null},[Q]:{configurable:!1,enumerable:!1,writable:!1,value:ie(i,s)}});const c=le(i,s);try{e(se(i,s),c)}catch(l){s.resolving?console.log("Unhandled exception in CancellablePromise executor.",l):c(l)}}cancel(e){return new a(n=>{Promise.all([this[Q](new G("Promise cancelled.",{cause:e})),Ne(this)]).then(()=>n(),()=>n())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>void this.cancel(e.reason),{capture:!0}),this}then(e,n,r){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(m(e)||(e=$),m(n)||(n=ee),e===$&&n==ee)return new a(i=>i(this));const o={};return this[f]=o,new a((i,s)=>{super.then(c=>{var l;this[f]===o&&(this[f]=null),(l=o.resolve)===null||l===void 0||l.call(o);try{i(e(c))}catch(d){s(d)}},c=>{var l;this[f]===o&&(this[f]=null),(l=o.resolve)===null||l===void 0||l.call(o);try{i(n(c))}catch(d){s(d)}})},async i=>{try{return r==null?void 0:r(i)}finally{await this.cancel(i)}})}catch(e,n){return this.then(void 0,e,n)}finally(e,n){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return m(e)?this.then(r=>a.resolve(e()).then(()=>r),r=>a.resolve(e()).then(()=>{throw r}),n):this.then(e,e,n)}static get[Z](){return Promise}static all(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.all(n).then(o,i)},o=>M(r,n,o));return r}static allSettled(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.allSettled(n).then(o,i)},o=>M(r,n,o));return r}static any(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.any(n).then(o,i)},o=>M(r,n,o));return r}static race(e){let n=Array.from(e);const r=new a((o,i)=>{Promise.race(n).then(o,i)},o=>M(r,n,o));return r}static cancel(e){const n=new a(()=>{});return n.cancel(e),n}static timeout(e,n){const r=new a(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>void r.cancel(n)):setTimeout(()=>void r.cancel(n),e),r}static sleep(e,n){return new a(r=>{setTimeout(()=>r(n),e)})}static reject(e){return new a((n,r)=>r(e))}static resolve(e){return e instanceof a?e:new a(n=>n(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new a((n,r)=>{e.resolve=n,e.reject=r},n=>{var r;(r=e.oncancelled)===null||r===void 0||r.call(e,n)}),e}}function ie(t,e){let n;return r=>{if(e.settled||(e.settled=!0,e.reason=r,t.reject(r),Promise.prototype.then.call(t.promise,void 0,o=>{if(o!==r)throw o})),!(!e.reason||!t.oncancelled))return n=new Promise(o=>{try{o(t.oncancelled(e.reason.cause))}catch(i){Promise.reject(new x(t.promise,i,"Unhandled exception in oncancelled callback."))}}).catch(o=>{Promise.reject(new x(t.promise,o,"Unhandled rejection in oncancelled callback."))}),t.oncancelled=null,n}}function se(t,e){return n=>{if(!e.resolving){if(e.resolving=!0,n===t.promise){if(e.settled)return;e.settled=!0,t.reject(new TypeError("A promise cannot be resolved with itself."));return}if(n!=null&&(typeof n=="object"||typeof n=="function")){let r;try{r=n.then}catch(o){e.settled=!0,t.reject(o);return}if(m(r)){try{let s=n.cancel;if(m(s)){const c=l=>{Reflect.apply(s,n,[l])};e.reason?ie(Object.assign(Object.assign({},t),{oncancelled:c}),e)(e.reason):t.oncancelled=c}}catch{}const o={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(s){this.root.settled=s},get reason(){return this.root.reason}},i=le(t,o);try{Reflect.apply(r,n,[se(t,o),i])}catch(s){i(s)}return}}e.settled||(e.settled=!0,t.resolve(n))}}}function le(t,e){return n=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(n instanceof G&&e.reason instanceof G&&Object.is(n.cause,e.reason.cause))return}catch{}Promise.reject(new x(t.promise,n))}else e.settled=!0,t.reject(n)}}function M(t,e,n){const r=[];for(const o of e){let i;try{if(!m(o.then)||(i=o.cancel,!m(i)))continue}catch{continue}let s;try{s=Reflect.apply(i,o,[n])}catch(c){Promise.reject(new x(t,c,"Unhandled exception in cancel method."));continue}s&&r.push((s instanceof Promise?s:Promise.resolve(s)).catch(c=>{Promise.reject(new x(t,c,"Unhandled rejection in cancel method."))}))}return Promise.all(r)}function $(t){return t}function ee(t){throw t}function Ue(t){try{if(t instanceof Error||typeof t!="object"||t.toString!==Object.prototype.toString)return""+t}catch{}try{return JSON.stringify(t)}catch{}try{return Object.prototype.toString.call(t)}catch{}return""}function Ne(t){var e;let n=(e=t[f])!==null&&e!==void 0?e:{};return"promise"in n||Object.assign(n,g()),t[f]==null&&(n.resolve(),t[f]=n),n.promise}let g=Promise.withResolvers;g&&typeof g=="function"?g=g.bind(Promise):g=function(){let t,e;return{promise:new Promise((r,o)=>{t=r,e=o}),resolve:t,reject:e}};window._wails=window._wails||{};window._wails.callResultHandler=Ve;window._wails.callErrorHandler=qe;const We=R(z.Call),Ge=R(z.CancelCall),b=new Map,Xe=0,Ye=0;class Je extends Error{constructor(e,n){super(e,n),this.name="RuntimeError"}}function Ve(t,e,n){const r=ce(t);if(r)if(!e)r.resolve(void 0);else if(!n)r.resolve(e);else try{r.resolve(JSON.parse(e))}catch(o){r.reject(new TypeError("could not parse result: "+o.message,{cause:o}))}}function qe(t,e,n){const r=ce(t);if(r)if(!n)r.reject(new Error(e));else{let o;try{o=JSON.parse(e)}catch(c){r.reject(new TypeError("could not parse error: "+c.message,{cause:c}));return}let i={};o.cause&&(i.cause=o.cause);let s;switch(o.kind){case"ReferenceError":s=new ReferenceError(o.message,i);break;case"TypeError":s=new TypeError(o.message,i);break;case"RuntimeError":s=new Je(o.message,i);break;default:s=new Error(o.message,i);break}r.reject(s)}}function ce(t){const e=b.get(t);return b.delete(t),e}function Ke(){let t;do t=te();while(b.has(t));return t}function Qe(t){const e=Ke(),n=a.withResolvers();b.set(e,{resolve:n.resolve,reject:n.reject});const r=We(Xe,Object.assign({"call-id":e},t));let o=!1;r.then(()=>{o=!0},s=>{b.delete(e),n.reject(s)});const i=()=>(b.delete(e),Ge(Ye,{"call-id":e}).catch(s=>{console.error("Error while requesting binding call cancellation:",s)}));return n.oncancelled=()=>o?i():r.then(i),n.promise}function V(t,...e){return Qe({methodID:t,args:e})}const w=new Map;class Ze{constructor(e,n,r){this.eventName=e,this.callback=n,this.maxCallbacks=r||-1}dispatch(e){try{this.callback(e)}catch(n){console.error(n)}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}}function $e(t){let e=w.get(t.eventName);e&&(e=e.filter(n=>n!==t),e.length===0?w.delete(t.eventName):w.set(t.eventName,e))}window._wails=window._wails||{};window._wails.dispatchWailsEvent=rt;const et=R(z.Events),tt=0;class nt{constructor(e,n=null){this.name=e,this.data=n}}function rt(t){let e=w.get(t.name);if(!e)return;let n=new nt(t.name,t.data);"sender"in t&&(n.sender=t.sender),e=e.filter(r=>!r.dispatch(n)),e.length===0?w.delete(t.name):w.set(t.name,e)}function ot(t,e,n){let r=w.get(t)||[];const o=new Ze(t,e,n);return r.push(o),w.set(t,r),()=>$e(o)}function it(t,e){return ot(t,e,-1)}function ae(t){return et(tt,t)}window._wails=window._wails||{};window._wails.invoke=k;k("wails:runtime:ready");function st(){return V(2374916939)}function lt(t){return V(784276339,t)}function ct(t,e){return V(3058653106,t,e)}class B{constructor(e={}){j(this,"R");j(this,"G");j(this,"B");j(this,"A");"R"in e||(this.R=0),"G"in e||(this.G=0),"B"in e||(this.B=0),"A"in e||(this.A=0),Object.assign(this,e)}static createFrom(e={}){let n=typeof e=="string"?JSON.parse(e):e;return new B(n)}}const at=document.getElementById("set-custom"),ut=document.getElementById("set"),dt=document.getElementById("remove"),ft=document.getElementById("set-go"),wt=document.getElementById("remove-go"),q=document.getElementById("label"),pt=document.getElementById("time");at.addEventListener("click",()=>{console.log("click!");let t=q.value;ct(t,{BackgroundColour:B.createFrom({R:0,G:255,B:255,A:255}),FontName:"arialb.ttf",FontSize:16,SmallFontSize:10,TextColour:B.createFrom({R:0,G:0,B:0,A:255})})});ut.addEventListener("click",()=>{let t=q.value;lt(t)});dt.addEventListener("click",()=>{st()});ft.addEventListener("click",()=>{let t=q.value;ae({name:"set:badge",data:t})});wt.addEventListener("click",()=>{ae({name:"remove:badge",data:null})});it("time",t=>{pt.innerText=t.data}); diff --git a/v3/examples/badge-custom/frontend/dist/index.html b/v3/examples/badge-custom/frontend/dist/index.html new file mode 100644 index 000000000..d785892ac --- /dev/null +++ b/v3/examples/badge-custom/frontend/dist/index.html @@ -0,0 +1,39 @@ + + + + + + + + Wails App + + + +
+ +

Wails + Typescript

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

Wails + Typescript

+
Set a badge label below ๐Ÿ‘‡
+
+
+ + + + + + +
+
+ +
+ + + diff --git a/v3/examples/badge-custom/frontend/package.json b/v3/examples/badge-custom/frontend/package.json new file mode 100644 index 000000000..b39da7ece --- /dev/null +++ b/v3/examples/badge-custom/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^5.0.0" + } +} diff --git a/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf b/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge-custom/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/badge-custom/frontend/public/style.css b/v3/examples/badge-custom/frontend/public/style.css new file mode 100644 index 000000000..6ce81cad2 --- /dev/null +++ b/v3/examples/badge-custom/frontend/public/style.css @@ -0,0 +1,155 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/public/typescript.svg b/v3/examples/badge-custom/frontend/public/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/examples/badge-custom/frontend/public/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/badge-custom/frontend/public/wails.png b/v3/examples/badge-custom/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/badge-custom/frontend/public/wails.png differ diff --git a/v3/examples/badge-custom/frontend/src/main.ts b/v3/examples/badge-custom/frontend/src/main.ts new file mode 100644 index 000000000..405f4fe28 --- /dev/null +++ b/v3/examples/badge-custom/frontend/src/main.ts @@ -0,0 +1,59 @@ +import {Events} from "@wailsio/runtime"; +import {SetBadge, RemoveBadge, SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice"; +import { RGBA } from "../bindings/image/color/models"; + +const setCustomButton = document.getElementById('set-custom')! as HTMLButtonElement; +const setButton = document.getElementById('set')! as HTMLButtonElement; +const removeButton = document.getElementById('remove')! as HTMLButtonElement; +const setButtonUsingGo = document.getElementById('set-go')! as HTMLButtonElement; +const removeButtonUsingGo = document.getElementById('remove-go')! as HTMLButtonElement; +const labelElement : HTMLInputElement = document.getElementById('label')! as HTMLInputElement; +const timeElement = document.getElementById('time')! as HTMLDivElement; + +setCustomButton.addEventListener('click', () => { + console.log("click!") + let label = (labelElement as HTMLInputElement).value + SetCustomBadge(label, { + BackgroundColour: RGBA.createFrom({ + R: 0, + G: 255, + B: 255, + A: 255, + }), + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: RGBA.createFrom({ + R: 0, + G: 0, + B: 0, + A: 255, + }), + }); +}) + +setButton.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + SetBadge(label); +}); + +removeButton.addEventListener('click', () => { + RemoveBadge(); +}); + +setButtonUsingGo.addEventListener('click', () => { + let label = (labelElement as HTMLInputElement).value + void Events.Emit({ + name: "set:badge", + data: label, + }) +}) + +removeButtonUsingGo.addEventListener('click', () => { + void Events.Emit({name:"remove:badge", data: null}) +}) + +Events.On('time', (time: {data: any}) => { + timeElement.innerText = time.data; +}); + diff --git a/v3/examples/badge-custom/frontend/src/vite-env.d.ts b/v3/examples/badge-custom/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/badge-custom/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/badge-custom/frontend/tsconfig.json b/v3/examples/badge-custom/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/badge-custom/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/badge-custom/main.go b/v3/examples/badge-custom/main.go new file mode 100644 index 000000000..e27103443 --- /dev/null +++ b/v3/examples/badge-custom/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "embed" + _ "embed" + "image/color" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/badge" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + + badgeService := badge.NewWithOptions(badge.Options{ + TextColour: color.RGBA{255, 255, 204, 255}, + BackgroundColour: color.RGBA{16, 124, 16, 255}, + FontName: "consolab.ttf", + FontSize: 20, + SmallFontSize: 14, + }) + + app := application.New(application.Options{ + Name: "badge", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(badgeService), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + app.Event.On("remove:badge", func(event *application.CustomEvent) { + err := badgeService.RemoveBadge() + if err != nil { + log.Fatal(err) + } + }) + + app.Event.On("set:badge", func(event *application.CustomEvent) { + text := event.Data.(string) + err := badgeService.SetBadge(text) + if err != nil { + log.Fatal(err) + } + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + case <-app.Context().Done(): + return + } + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/badge/README.md b/v3/examples/badge/README.md new file mode 100644 index 000000000..abb7b9653 --- /dev/null +++ b/v3/examples/badge/README.md @@ -0,0 +1,71 @@ +# Welcome to Your New Wails3 Project! +Now that you have your project set up, it's time to explore the basic badge features that Wails3 offers on **macOS** and **Windows**. + +## Exploring Badge Features + +### Creating the Service + +First, initialize the badge service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/badge" + +// Create a new badge service +badgeService := badge.New() + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(badgeService), + }, +}) +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon: + +#### Go +```go +// Set a default badge +badgeService.SetBadge("") + +// Set a numeric badge +badgeService.SetBadge("3") + +// Set a text badge +badgeService.SetBadge("New") +``` + +#### JS +```js +import {SetBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +// Set a default badge +SetBadge("") + +// Set a numeric badge +SetBadge("3") + +// Set a text badge +SetBadge("New") +``` + +### Removing a Badge + +Remove the badge from the application icon: + +#### Go +```go +badgeService.RemoveBadge() +``` + +#### JS +```js +import {RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service"; + +RemoveBadge() +``` \ No newline at end of file diff --git a/v3/examples/badge/Taskfile.yml b/v3/examples/badge/Taskfile.yml new file mode 100644 index 000000000..d1bcfaacf --- /dev/null +++ b/v3/examples/badge/Taskfile.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "badge" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + diff --git a/v3/examples/badge/build/Taskfile.yml b/v3/examples/badge/build/Taskfile.yml new file mode 100644 index 000000000..5f3517efc --- /dev/null +++ b/v3/examples/badge/build/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + label: build:frontend (PRODUCTION={{.PRODUCTION}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - npm run {{.BUILD_COMMAND}} -q + env: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + vars: + BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}' + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - npm run dev -- --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . diff --git a/v3/examples/badge/build/appicon.png b/v3/examples/badge/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/badge/build/appicon.png differ diff --git a/v3/examples/badge/build/config.yml b/v3/examples/badge/build/config.yml new file mode 100644 index 000000000..aaa3240fb --- /dev/null +++ b/v3/examples/badge/build/config.yml @@ -0,0 +1,63 @@ +# This file contains the configuration for this project. +# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. +# Note that this will overwrite any changes you have made to the assets. +version: '3' + +# This information is used to generate the build assets. +info: + companyName: "My Company" # The name of the company + productName: "My Product" # The name of the application + productIdentifier: "com.mycompany.myproduct" # The unique product identifier + description: "A program that does X" # The application description + copyright: "(c) 2025, My Company" # Copyright text + comments: "Some Product Comments" # Comments + version: "0.0.1" # The application version + +# Dev mode configuration +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary + +# File Associations +# More information at: https://v3.wails.io/noit/done/yet +fileAssociations: +# - ext: wails +# name: Wails +# description: Wails Application File +# iconName: wailsFileIcon +# role: Editor +# - ext: jpg +# name: JPEG +# description: Image File +# iconName: jpegFileIcon +# role: Editor +# mimeType: image/jpeg # (optional) + +# Other data +other: + - name: My Other Data \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Info.dev.plist b/v3/examples/badge/build/darwin/Info.dev.plist new file mode 100644 index 000000000..4caddf720 --- /dev/null +++ b/v3/examples/badge/build/darwin/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + ยฉ now, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Info.plist b/v3/examples/badge/build/darwin/Info.plist new file mode 100644 index 000000000..0dc90b2e7 --- /dev/null +++ b/v3/examples/badge/build/darwin/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product + CFBundleExecutable + badge + CFBundleIdentifier + com.wails.badge + CFBundleVersion + 0.1.0 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.1.0 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + ยฉ now, My Company + + \ No newline at end of file diff --git a/v3/examples/badge/build/darwin/Taskfile.yml b/v3/examples/badge/build/darwin/Taskfile.yml new file mode 100644 index 000000000..f0791fea9 --- /dev/null +++ b/v3/examples/badge/build/darwin/Taskfile.yml @@ -0,0 +1,81 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Creates a production build of the application + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages a production build of the application into a `.app` bundle + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS + - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app + + run: + cmds: + - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources} + - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources + - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS + - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist + - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' diff --git a/v3/examples/badge/build/darwin/icons.icns b/v3/examples/badge/build/darwin/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/badge/build/darwin/icons.icns differ diff --git a/v3/examples/badge/build/linux/Taskfile.yml b/v3/examples/badge/build/linux/Taskfile.yml new file mode 100644 index 000000000..560cc9c92 --- /dev/null +++ b/v3/examples/badge/build/linux/Taskfile.yml @@ -0,0 +1,119 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Linux + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application for Linux + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + vars: + PRODUCTION: "true" + - task: generate:dotdesktop + cmds: + - cp {{.APP_BINARY}} {{.APP_NAME}} + - cp ../../appicon.png appicon.png + - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '../../appicon.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}" + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: 'appicon' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' diff --git a/v3/examples/badge/build/linux/appimage/build.sh b/v3/examples/badge/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/v3/examples/badge/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/v3/examples/badge/build/linux/nfpm/nfpm.yaml b/v3/examples/badge/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..5b7ea9d02 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,50 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "badge" +arch: ${GOARCH} +platform: "linux" +version: "0.1.0" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "My Product Description" +vendor: "My Company" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/badge" + dst: "/usr/local/bin/badge" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/badge.png" + - src: "./build/linux/badge.desktop" + dst: "/usr/share/applications/badge.desktop" + +depends: + - gtk3 + - libwebkit2gtk + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" +# scripts: +# preinstall: ./build/linux/nfpm/scripts/preinstall.sh +# postinstall: ./build/linux/nfpm/scripts/postinstall.sh +# preremove: ./build/linux/nfpm/scripts/preremove.sh +# postremove: ./build/linux/nfpm/scripts/postremove.sh diff --git a/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh b/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh b/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/v3/examples/badge/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/v3/examples/badge/build/windows/Taskfile.yml b/v3/examples/badge/build/windows/Taskfile.yml new file mode 100644 index 000000000..534f4fb31 --- /dev/null +++ b/v3/examples/badge/build/windows/Taskfile.yml @@ -0,0 +1,63 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +tasks: + build: + summary: Builds the application for Windows + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 0 + GOARCH: '{{.ARCH | default ARCH}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + summary: Packages a production build of the application into a `.exe` bundle + cmds: + - task: create:nsis:installer + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' diff --git a/v3/examples/badge/build/windows/icon.ico b/v3/examples/badge/build/windows/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/badge/build/windows/icon.ico differ diff --git a/v3/examples/badge/build/windows/info.json b/v3/examples/badge/build/windows/info.json new file mode 100644 index 000000000..850b2b5b0 --- /dev/null +++ b/v3/examples/badge/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.1.0" + }, + "info": { + "0000": { + "ProductVersion": "0.1.0", + "CompanyName": "My Company", + "FileDescription": "My Product Description", + "LegalCopyright": "ยฉ now, My Company", + "ProductName": "My Product", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/badge/build/windows/nsis/project.nsi b/v3/examples/badge/build/windows/nsis/project.nsi new file mode 100644 index 000000000..985b8e207 --- /dev/null +++ b/v3/examples/badge/build/windows/nsis/project.nsi @@ -0,0 +1,112 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "badge" +## !define INFO_COMPANYNAME "My Company" # Default "My Company" +## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "ยฉ now, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/v3/examples/badge/build/windows/nsis/wails_tools.nsh b/v3/examples/badge/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..6fc10ab79 --- /dev/null +++ b/v3/examples/badge/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,212 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "badge" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "My Company" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "My Product" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.1.0" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "ยฉ now, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend \ No newline at end of file diff --git a/v3/examples/badge/build/windows/wails.exe.manifest b/v3/examples/badge/build/windows/wails.exe.manifest new file mode 100644 index 000000000..fcfd2fc46 --- /dev/null +++ b/v3/examples/badge/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/badge/frontend/Inter Font License.txt b/v3/examples/badge/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/examples/badge/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts new file mode 100644 index 000000000..f959d50dc --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/badgeservice.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Service represents the notifications service + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * RemoveBadge removes the badge label from the application icon. + */ +export function RemoveBadge(): $CancellablePromise { + return $Call.ByID(2374916939); +} + +/** + * SetBadge sets the badge label on the application icon. + */ +export function SetBadge(label: string): $CancellablePromise { + return $Call.ByID(784276339, label); +} + +export function SetCustomBadge(label: string, options: $models.Options): $CancellablePromise { + return $Call.ByID(3058653106, label, options); +} diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts new file mode 100644 index 000000000..df3ea9723 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as BadgeService from "./badgeservice.js"; +export { + BadgeService +}; + +export { + Options +} from "./models.js"; diff --git a/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts new file mode 100644 index 000000000..67ed264c0 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/badge/models.ts @@ -0,0 +1,58 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as color$0 from "../../../../../../../image/color/models.js"; + +export class Options { + "TextColour": color$0.RGBA; + "BackgroundColour": color$0.RGBA; + "FontName": string; + "FontSize": number; + "SmallFontSize": number; + + /** Creates a new Options instance. */ + constructor($$source: Partial = {}) { + if (!("TextColour" in $$source)) { + this["TextColour"] = (new color$0.RGBA()); + } + if (!("BackgroundColour" in $$source)) { + this["BackgroundColour"] = (new color$0.RGBA()); + } + if (!("FontName" in $$source)) { + this["FontName"] = ""; + } + if (!("FontSize" in $$source)) { + this["FontSize"] = 0; + } + if (!("SmallFontSize" in $$source)) { + this["SmallFontSize"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Options instance from a string or object. + */ + static createFrom($$source: any = {}): Options { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("TextColour" in $$parsedSource) { + $$parsedSource["TextColour"] = $$createField0_0($$parsedSource["TextColour"]); + } + if ("BackgroundColour" in $$parsedSource) { + $$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]); + } + return new Options($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = color$0.RGBA.createFrom; diff --git a/v3/examples/badge/frontend/bindings/image/color/index.ts b/v3/examples/badge/frontend/bindings/image/color/index.ts new file mode 100644 index 000000000..97b507b08 --- /dev/null +++ b/v3/examples/badge/frontend/bindings/image/color/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + RGBA +} from "./models.js"; diff --git a/v3/examples/badge/frontend/bindings/image/color/models.ts b/v3/examples/badge/frontend/bindings/image/color/models.ts new file mode 100644 index 000000000..0d4eab56d --- /dev/null +++ b/v3/examples/badge/frontend/bindings/image/color/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * RGBA represents a traditional 32-bit alpha-premultiplied color, having 8 + * bits for each of red, green, blue and alpha. + * + * An alpha-premultiplied color component C has been scaled by alpha (A), so + * has valid values 0 <= C <= A. + */ +export class RGBA { + "R": number; + "G": number; + "B": number; + "A": number; + + /** Creates a new RGBA instance. */ + constructor($$source: Partial = {}) { + if (!("R" in $$source)) { + this["R"] = 0; + } + if (!("G" in $$source)) { + this["G"] = 0; + } + if (!("B" in $$source)) { + this["B"] = 0; + } + if (!("A" in $$source)) { + this["A"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new RGBA instance from a string or object. + */ + static createFrom($$source: any = {}): RGBA { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new RGBA($$parsedSource as Partial); + } +} diff --git a/v3/examples/badge/frontend/dist/Inter-Medium.ttf b/v3/examples/badge/frontend/dist/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/badge/frontend/dist/Inter-Medium.ttf differ diff --git a/v3/examples/badge/frontend/dist/assets/index-djydTGr9.js b/v3/examples/badge/frontend/dist/assets/index-djydTGr9.js new file mode 100644 index 000000000..130eddeb5 --- /dev/null +++ b/v3/examples/badge/frontend/dist/assets/index-djydTGr9.js @@ -0,0 +1,6 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();const ce="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function $(t=21){let e="",n=t|0;for(;n--;)e+=ce[Math.random()*64|0];return e}const ae=window.location.origin+"/wails/runtime",C=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10});let ue=$();function z(t,e=""){return function(n,r=null){return de(t,n,e,r)}}async function de(t,e,n,r){var o,i;let s=new URL(ae);s.searchParams.append("object",t.toString()),s.searchParams.append("method",e.toString()),r&&s.searchParams.append("args",JSON.stringify(r));let c={"x-wails-client-id":ue};n&&(c["x-wails-window-name"]=n);let l=await fetch(s,{headers:c});if(!l.ok)throw new Error(await l.text());return((i=(o=l.headers.get("Content-Type"))===null||o===void 0?void 0:o.indexOf("application/json"))!==null&&i!==void 0?i:-1)!==-1?l.json():l.text()}z(C.System);const P=function(){var t,e,n,r,o;try{if(!((e=(t=window.chrome)===null||t===void 0?void 0:t.webview)===null||e===void 0)&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if(!((o=(r=(n=window.webkit)===null||n===void 0?void 0:n.messageHandlers)===null||r===void 0?void 0:r.external)===null||o===void 0)&&o.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch{}return console.warn(` +%cโš ๏ธ Browser Environment Detected %c + +%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode. +More information at: https://v3.wails.io/learn/build/#using-a-browser-for-development +`,"background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;","background: transparent;","color: #ffffff; font-style: italic; font-weight: bold;"),null}();function R(t){P==null||P(t)}function Q(){return window._wails.environment.OS==="windows"}function fe(){return!!window._wails.environment.Debug}function we(){return new MouseEvent("mousedown").buttons===0}function Z(t){var e;return t.target instanceof HTMLElement?t.target:!(t.target instanceof HTMLElement)&&t.target instanceof Node&&(e=t.target.parentElement)!==null&&e!==void 0?e:document.body}document.addEventListener("DOMContentLoaded",()=>{});window.addEventListener("contextmenu",ge);const pe=z(C.ContextMenu),me=0;function he(t,e,n,r){pe(me,{id:t,x:e,y:n,data:r})}function ge(t){const e=Z(t),n=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(n){t.preventDefault();const r=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");he(n,t.clientX,t.clientY,r)}else ye(t,e)}function ye(t,e){if(fe())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":t.preventDefault();return}if(e.isContentEditable)return;const n=window.getSelection(),r=n&&n.toString().length>0;if(r)for(let o=0;o{F=t,F||(v=E=!1,u())};window.addEventListener("mousedown",N,{capture:!0});window.addEventListener("mousemove",N,{capture:!0});window.addEventListener("mouseup",N,{capture:!0});for(const t of["click","contextmenu","dblclick"])window.addEventListener(t,be,{capture:!0});function be(t){(S||E)&&(t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault())}const B=0,ve=1,D=2;function N(t){let e,n=t.buttons;switch(t.type){case"mousedown":e=B,H||(n=h|1<"u"||typeof e=="object"))try{var n=L.call(e);return(n===Le||n===Re||n===Te||n===ze)&&e("")==null}catch{}return!1})}function He(t){if(Y(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;try{y(t,null,_)}catch(e){if(e!==O)return!1}return!X(t)&&U(t)}function Be(t){if(Y(t))return!0;if(!t||typeof t!="function"&&typeof t!="object")return!1;if(ke)return U(t);if(X(t))return!1;var e=L.call(t);return e!==Me&&e!==Oe&&!/^\[object HTML/.test(e)?!1:U(t)}const m=y?He:Be;var I;class W extends Error{constructor(e,n){super(e,n),this.name="CancelError"}}class x extends Error{constructor(e,n,r){super((r??"Unhandled rejection in cancelled promise.")+" Reason: "+De(n),{cause:n}),this.promise=e,this.name="CancelledRejectionError"}}const f=Symbol("barrier"),J=Symbol("cancelImpl"),V=(I=Symbol.species)!==null&&I!==void 0?I:Symbol("speciesPolyfill");class a extends Promise{constructor(e,n){let r,o;if(super((l,d)=>{r=l,o=d}),this.constructor[V]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let i={promise:this,resolve:r,reject:o,get oncancelled(){return n??null},set oncancelled(l){n=l??void 0}};const s={get root(){return s},resolving:!1,settled:!1};Object.defineProperties(this,{[f]:{configurable:!1,enumerable:!1,writable:!0,value:null},[J]:{configurable:!1,enumerable:!1,writable:!1,value:te(i,s)}});const c=re(i,s);try{e(ne(i,s),c)}catch(l){s.resolving?console.log("Unhandled exception in CancellablePromise executor.",l):c(l)}}cancel(e){return new a(n=>{Promise.all([this[J](new W("Promise cancelled.",{cause:e})),Ie(this)]).then(()=>n(),()=>n())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>void this.cancel(e.reason),{capture:!0}),this}then(e,n,r){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(m(e)||(e=q),m(n)||(n=K),e===q&&n==K)return new a(i=>i(this));const o={};return this[f]=o,new a((i,s)=>{super.then(c=>{var l;this[f]===o&&(this[f]=null),(l=o.resolve)===null||l===void 0||l.call(o);try{i(e(c))}catch(d){s(d)}},c=>{var l;this[f]===o&&(this[f]=null),(l=o.resolve)===null||l===void 0||l.call(o);try{i(n(c))}catch(d){s(d)}})},async i=>{try{return r==null?void 0:r(i)}finally{await this.cancel(i)}})}catch(e,n){return this.then(void 0,e,n)}finally(e,n){if(!(this instanceof a))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return m(e)?this.then(r=>a.resolve(e()).then(()=>r),r=>a.resolve(e()).then(()=>{throw r}),n):this.then(e,e,n)}static get[V](){return Promise}static all(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.all(n).then(o,i)},o=>M(r,n,o));return r}static allSettled(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.allSettled(n).then(o,i)},o=>M(r,n,o));return r}static any(e){let n=Array.from(e);const r=n.length===0?a.resolve(n):new a((o,i)=>{Promise.any(n).then(o,i)},o=>M(r,n,o));return r}static race(e){let n=Array.from(e);const r=new a((o,i)=>{Promise.race(n).then(o,i)},o=>M(r,n,o));return r}static cancel(e){const n=new a(()=>{});return n.cancel(e),n}static timeout(e,n){const r=new a(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>void r.cancel(n)):setTimeout(()=>void r.cancel(n),e),r}static sleep(e,n){return new a(r=>{setTimeout(()=>r(n),e)})}static reject(e){return new a((n,r)=>r(e))}static resolve(e){return e instanceof a?e:new a(n=>n(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new a((n,r)=>{e.resolve=n,e.reject=r},n=>{var r;(r=e.oncancelled)===null||r===void 0||r.call(e,n)}),e}}function te(t,e){let n;return r=>{if(e.settled||(e.settled=!0,e.reason=r,t.reject(r),Promise.prototype.then.call(t.promise,void 0,o=>{if(o!==r)throw o})),!(!e.reason||!t.oncancelled))return n=new Promise(o=>{try{o(t.oncancelled(e.reason.cause))}catch(i){Promise.reject(new x(t.promise,i,"Unhandled exception in oncancelled callback."))}}).catch(o=>{Promise.reject(new x(t.promise,o,"Unhandled rejection in oncancelled callback."))}),t.oncancelled=null,n}}function ne(t,e){return n=>{if(!e.resolving){if(e.resolving=!0,n===t.promise){if(e.settled)return;e.settled=!0,t.reject(new TypeError("A promise cannot be resolved with itself."));return}if(n!=null&&(typeof n=="object"||typeof n=="function")){let r;try{r=n.then}catch(o){e.settled=!0,t.reject(o);return}if(m(r)){try{let s=n.cancel;if(m(s)){const c=l=>{Reflect.apply(s,n,[l])};e.reason?te(Object.assign(Object.assign({},t),{oncancelled:c}),e)(e.reason):t.oncancelled=c}}catch{}const o={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(s){this.root.settled=s},get reason(){return this.root.reason}},i=re(t,o);try{Reflect.apply(r,n,[ne(t,o),i])}catch(s){i(s)}return}}e.settled||(e.settled=!0,t.resolve(n))}}}function re(t,e){return n=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(n instanceof W&&e.reason instanceof W&&Object.is(n.cause,e.reason.cause))return}catch{}Promise.reject(new x(t.promise,n))}else e.settled=!0,t.reject(n)}}function M(t,e,n){const r=[];for(const o of e){let i;try{if(!m(o.then)||(i=o.cancel,!m(i)))continue}catch{continue}let s;try{s=Reflect.apply(i,o,[n])}catch(c){Promise.reject(new x(t,c,"Unhandled exception in cancel method."));continue}s&&r.push((s instanceof Promise?s:Promise.resolve(s)).catch(c=>{Promise.reject(new x(t,c,"Unhandled rejection in cancel method."))}))}return Promise.all(r)}function q(t){return t}function K(t){throw t}function De(t){try{if(t instanceof Error||typeof t!="object"||t.toString!==Object.prototype.toString)return""+t}catch{}try{return JSON.stringify(t)}catch{}try{return Object.prototype.toString.call(t)}catch{}return""}function Ie(t){var e;let n=(e=t[f])!==null&&e!==void 0?e:{};return"promise"in n||Object.assign(n,g()),t[f]==null&&(n.resolve(),t[f]=n),n.promise}let g=Promise.withResolvers;g&&typeof g=="function"?g=g.bind(Promise):g=function(){let t,e;return{promise:new Promise((r,o)=>{t=r,e=o}),resolve:t,reject:e}};window._wails=window._wails||{};window._wails.callResultHandler=Xe;window._wails.callErrorHandler=Ye;const Fe=z(C.Call),_e=z(C.CancelCall),b=new Map,Ue=0,We=0;class Ne extends Error{constructor(e,n){super(e,n),this.name="RuntimeError"}}function Xe(t,e,n){const r=oe(t);if(r)if(!e)r.resolve(void 0);else if(!n)r.resolve(e);else try{r.resolve(JSON.parse(e))}catch(o){r.reject(new TypeError("could not parse result: "+o.message,{cause:o}))}}function Ye(t,e,n){const r=oe(t);if(r)if(!n)r.reject(new Error(e));else{let o;try{o=JSON.parse(e)}catch(c){r.reject(new TypeError("could not parse error: "+c.message,{cause:c}));return}let i={};o.cause&&(i.cause=o.cause);let s;switch(o.kind){case"ReferenceError":s=new ReferenceError(o.message,i);break;case"TypeError":s=new TypeError(o.message,i);break;case"RuntimeError":s=new Ne(o.message,i);break;default:s=new Error(o.message,i);break}r.reject(s)}}function oe(t){const e=b.get(t);return b.delete(t),e}function Ge(){let t;do t=$();while(b.has(t));return t}function Je(t){const e=Ge(),n=a.withResolvers();b.set(e,{resolve:n.resolve,reject:n.reject});const r=Fe(Ue,Object.assign({"call-id":e},t));let o=!1;r.then(()=>{o=!0},s=>{b.delete(e),n.reject(s)});const i=()=>(b.delete(e),_e(We,{"call-id":e}).catch(s=>{console.error("Error while requesting binding call cancellation:",s)}));return n.oncancelled=()=>o?i():r.then(i),n.promise}function ie(t,...e){return Je({methodID:t,args:e})}const w=new Map;class Ve{constructor(e,n,r){this.eventName=e,this.callback=n,this.maxCallbacks=r||-1}dispatch(e){try{this.callback(e)}catch(n){console.error(n)}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}}function qe(t){let e=w.get(t.eventName);e&&(e=e.filter(n=>n!==t),e.length===0?w.delete(t.eventName):w.set(t.eventName,e))}window._wails=window._wails||{};window._wails.dispatchWailsEvent=Ze;const Ke=z(C.Events),$e=0;class Qe{constructor(e,n=null){this.name=e,this.data=n}}function Ze(t){let e=w.get(t.name);if(!e)return;let n=new Qe(t.name,t.data);"sender"in t&&(n.sender=t.sender),e=e.filter(r=>!r.dispatch(n)),e.length===0?w.delete(t.name):w.set(t.name,e)}function et(t,e,n){let r=w.get(t)||[];const o=new Ve(t,e,n);return r.push(o),w.set(t,r),()=>qe(o)}function tt(t,e){return et(t,e,-1)}function se(t){return Ke($e,t)}window._wails=window._wails||{};window._wails.invoke=R;R("wails:runtime:ready");function nt(){return ie(2374916939)}function rt(t){return ie(784276339,t)}const ot=document.getElementById("set"),it=document.getElementById("remove"),st=document.getElementById("set-go"),lt=document.getElementById("remove-go"),le=document.getElementById("label"),ct=document.getElementById("time");ot.addEventListener("click",()=>{let t=le.value;rt(t)});it.addEventListener("click",()=>{nt()});st.addEventListener("click",()=>{let t=le.value;se({name:"set:badge",data:t})});lt.addEventListener("click",()=>{se({name:"remove:badge",data:null})});tt("time",t=>{ct.innerText=t.data}); diff --git a/v3/examples/badge/frontend/dist/index.html b/v3/examples/badge/frontend/dist/index.html new file mode 100644 index 000000000..d5b44d109 --- /dev/null +++ b/v3/examples/badge/frontend/dist/index.html @@ -0,0 +1,38 @@ + + + + + + + + Wails App + + + +
+ +

Wails + Typescript

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

Wails + Typescript

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

Documentation

+

Feedback

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

A MacTitleBarHiddenInset WebviewWindow example

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

A MacTitleBarHiddenInsetUnified WebviewWindow example

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

A MacTitleBarHidden WebviewWindow example

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

Documentation

+

Feedback

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

Documentation

+

Feedback

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

1

+
+
+

2

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

Context Menu Demo

+

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

go run -tags production .
+ +
+
+

1

+
+
+

2

+
+
+

Default Context Menu shown here

+ +
+
+

Default auto (smart) Context Menu here

+ +
+
+

Context menu shown here only if you select text

+

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

+
+
+
+
+
+
+ + + + +

content editable

+
+
+
+

Default Context Menu hidden here

+

Context Menu hidden here even if text is selected

+ +
+
+

Nested section reverted to auto (smart) default Context Menu

+

Context menu shown here only if you select text

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

Custom Protocol / Deep Link Test

+

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

+

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

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

Custom Protocol / Deep Link Test

+

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

+

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

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

Wails + Svelte

+ +
+ +
+ +

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

+ +

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

+
+ + diff --git a/v3/examples/dev/frontend/src/app.css b/v3/examples/dev/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/examples/dev/frontend/src/app.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/v3/examples/dev/frontend/src/assets/svelte.svg b/v3/examples/dev/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/examples/dev/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/dev/frontend/src/lib/Counter.svelte b/v3/examples/dev/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..e45f90310 --- /dev/null +++ b/v3/examples/dev/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/v3/examples/dev/frontend/src/main.js b/v3/examples/dev/frontend/src/main.js new file mode 100644 index 000000000..8a909a15a --- /dev/null +++ b/v3/examples/dev/frontend/src/main.js @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/v3/examples/dev/frontend/src/vite-env.d.ts b/v3/examples/dev/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/v3/examples/dev/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/v3/examples/dev/frontend/vite.config.js b/v3/examples/dev/frontend/vite.config.js new file mode 100644 index 000000000..d70196943 --- /dev/null +++ b/v3/examples/dev/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/v3/examples/dev/go.mod b/v3/examples/dev/go.mod new file mode 100644 index 000000000..f91cd7357 --- /dev/null +++ b/v3/examples/dev/go.mod @@ -0,0 +1,50 @@ +module changeme + +go 1.24.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/dev/go.sum b/v3/examples/dev/go.sum new file mode 100644 index 000000000..cbadfe003 --- /dev/null +++ b/v3/examples/dev/go.sum @@ -0,0 +1,146 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/dev/main.go b/v3/examples/dev/main.go new file mode 100644 index 000000000..111273721 --- /dev/null +++ b/v3/examples/dev/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "dev", + Description: "A demo of using raw HTML & CSS", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + + URL: "/", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/dialogs-basic/.hidden_file b/v3/examples/dialogs-basic/.hidden_file new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/dialogs-basic/README.md b/v3/examples/dialogs-basic/README.md new file mode 100644 index 000000000..c03911376 --- /dev/null +++ b/v3/examples/dialogs-basic/README.md @@ -0,0 +1,36 @@ +# Dialog Test Application + +This application is designed to test macOS file dialog functionality across different versions of macOS. It provides a comprehensive suite of tests for various dialog features and configurations. + +## Features Tested + +1. Basic file open dialog +2. Single extension filter +3. Multiple extension filter +4. Multiple file selection +5. Directory selection +6. Save dialog with extension +7. Complex filters +8. Hidden files +9. Default directory +10. Full featured dialog with all options + +## Running the Tests + +```bash +go run main.go +``` + +## Test Results + +When running tests: +- Each test will show the selected file(s) and their types +- For multiple selections, all selected files will be listed +- Errors will be displayed in an error dialog +- The application logs debug information to help track issues + +## Notes + +- This test application is primarily for development and testing purposes +- It can be used to verify dialog behavior across different macOS versions +- The tests are designed to not interfere with CI pipelines diff --git a/v3/examples/dialogs-basic/main.go b/v3/examples/dialogs-basic/main.go new file mode 100644 index 000000000..8df5a1c37 --- /dev/null +++ b/v3/examples/dialogs-basic/main.go @@ -0,0 +1,260 @@ +package main + +import ( + "fmt" + "log" + "log/slog" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Dialog Test", + Description: "Test application for macOS dialogs", + Logger: application.DefaultLogger(slog.LevelDebug), + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create main window + mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Dialog Tests", + Width: 800, + Height: 600, + MinWidth: 800, + MinHeight: 600, + }) + mainWindow.SetAlwaysOnTop(true) + + // Create main menu + menu := app.NewMenu() + app.Menu.Set(menu) + menu.AddRole(application.AppMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + + // Add test menu + testMenu := menu.AddSubmenu("Tests") + + // Test 1: Basic file open with no filters (no window) + testMenu.Add("1. Basic Open (No Window)").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + PromptForSingleSelection() + showResult("Basic Open", result, err, nil) + }) + + // Test 1b: Basic file open with window + testMenu.Add("1b. Basic Open (With Window)").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Basic Open", result, err, mainWindow) + }) + + // Test 2: Open with single extension filter + testMenu.Add("2. Single Filter").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + AddFilter("Text Files", "*.txt"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Single Filter", result, err, mainWindow) + }) + + // Test 3: Open with multiple extension filter + testMenu.Add("3. Multiple Filter").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + AddFilter("Documents", "*.txt;*.md;*.doc;*.docx"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Multiple Filter", result, err, mainWindow) + }) + + // Test 4: Multiple file selection + testMenu.Add("4. Multiple Selection").OnClick(func(ctx *application.Context) { + results, err := application.OpenFileDialog(). + CanChooseFiles(true). + AddFilter("Images", "*.png;*.jpg;*.jpeg"). + AttachToWindow(mainWindow). + PromptForMultipleSelection() + if err != nil { + showError("Multiple Selection", err, mainWindow) + return + } + showResults("Multiple Selection", results, mainWindow) + }) + + // Test 5: Directory selection + testMenu.Add("5. Directory Selection").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseDirectories(true). + CanChooseFiles(false). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Directory Selection", result, err, mainWindow) + }) + + // Test 6: Save dialog with extension + testMenu.Add("6. Save Dialog").OnClick(func(ctx *application.Context) { + result, err := application.SaveFileDialog(). + SetFilename("test.txt"). + AddFilter("Text Files", "*.txt"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Save Dialog", result, err, mainWindow) + }) + + // Test 7: Complex filters + testMenu.Add("7. Complex Filters").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + AddFilter("All Documents", "*.txt;*.md;*.doc;*.docx;*.pdf"). + AddFilter("Text Files", "*.txt"). + AddFilter("Markdown", "*.md"). + AddFilter("Word Documents", "*.doc;*.docx"). + AddFilter("PDF Files", "*.pdf"). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Complex Filters", result, err, mainWindow) + }) + + // Test 8: Hidden files + testMenu.Add("8. Show Hidden").OnClick(func(ctx *application.Context) { + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + ShowHiddenFiles(true). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Show Hidden", result, err, mainWindow) + }) + + // Test 9: Default directory + testMenu.Add("9. Default Directory").OnClick(func(ctx *application.Context) { + home, _ := os.UserHomeDir() + result, err := application.OpenFileDialog(). + CanChooseFiles(true). + SetDirectory(home). + AttachToWindow(mainWindow). + PromptForSingleSelection() + showResult("Default Directory", result, err, mainWindow) + }) + + // Test 10: Full featured dialog + testMenu.Add("10. Full Featured").OnClick(func(ctx *application.Context) { + home, _ := os.UserHomeDir() + dialog := application.OpenFileDialog(). + SetTitle("Full Featured Dialog"). + SetDirectory(home). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + ResolvesAliases(true). + AllowsOtherFileTypes(true). + AttachToWindow(mainWindow) + + if runtime.GOOS == "darwin" { + dialog.SetMessage("Please select files") + } + + dialog.AddFilter("All Supported", "*.txt;*.md;*.pdf;*.png;*.jpg") + dialog.AddFilter("Documents", "*.txt;*.md;*.pdf") + dialog.AddFilter("Images", "*.png;*.jpg;*.jpeg") + + results, err := dialog.PromptForMultipleSelection() + if err != nil { + showError("Full Featured", err, mainWindow) + return + } + showResults("Full Featured", results, mainWindow) + }) + + // Show the window + mainWindow.Show() + + // Run the app + if err := app.Run(); err != nil { + log.Fatal(err) + } +} + +func showResult(test string, result string, err error, window *application.WebviewWindow) { + if err != nil { + showError(test, err, window) + return + } + if result == "" { + dialog := application.InfoDialog(). + SetTitle(test). + SetMessage("No file selected") + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() + return + } + dialog := application.InfoDialog(). + SetTitle(test). + SetMessage(fmt.Sprintf("Selected: %s\nType: %s", result, getFileType(result))) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func showResults(test string, results []string, window *application.WebviewWindow) { + if len(results) == 0 { + dialog := application.InfoDialog(). + SetTitle(test). + SetMessage("No files selected") + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() + return + } + var message strings.Builder + message.WriteString(fmt.Sprintf("Selected %d files:\n\n", len(results))) + for _, result := range results { + message.WriteString(fmt.Sprintf("%s (%s)\n", result, getFileType(result))) + } + dialog := application.InfoDialog(). + SetTitle(test). + SetMessage(message.String()) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func showError(test string, err error, window *application.WebviewWindow) { + dialog := application.ErrorDialog(). + SetTitle(test). + SetMessage(fmt.Sprintf("Error: %v", err)) + if window != nil { + dialog.AttachToWindow(window) + } + dialog.Show() +} + +func getFileType(path string) string { + if path == "" { + return "unknown" + } + ext := strings.ToLower(filepath.Ext(path)) + if ext == "" { + if fi, err := os.Stat(path); err == nil && fi.IsDir() { + return "directory" + } + return "no extension" + } + return ext +} diff --git a/v3/examples/dialogs-basic/test.txt b/v3/examples/dialogs-basic/test.txt new file mode 100644 index 000000000..64b89ccaf --- /dev/null +++ b/v3/examples/dialogs-basic/test.txt @@ -0,0 +1 @@ +This is a sample text file to test filtering. \ No newline at end of file diff --git a/v3/examples/dialogs-basic/wails-logo-small.jpg b/v3/examples/dialogs-basic/wails-logo-small.jpg new file mode 100644 index 000000000..29cb1129e Binary files /dev/null and b/v3/examples/dialogs-basic/wails-logo-small.jpg differ diff --git a/v3/examples/dialogs-basic/wails-logo-small.png b/v3/examples/dialogs-basic/wails-logo-small.png new file mode 100644 index 000000000..cbd40bf90 Binary files /dev/null and b/v3/examples/dialogs-basic/wails-logo-small.png differ diff --git a/v3/examples/dialogs/README.md b/v3/examples/dialogs/README.md new file mode 100644 index 000000000..d80afc91a --- /dev/null +++ b/v3/examples/dialogs/README.md @@ -0,0 +1,33 @@ +# Dialogs Example + +This example is a comprehensive example of using dialogs in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +To build the example to use application icons, simply run the following command: + +```bash +wails3 task package +``` + +# Status + +| Platform | Status | +|----------|----------------| +| Mac | Mostly Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/dialogs/Taskfile.yml b/v3/examples/dialogs/Taskfile.yml new file mode 100644 index 000000000..90ac01145 --- /dev/null +++ b/v3/examples/dialogs/Taskfile.yml @@ -0,0 +1,107 @@ +version: '3' + +vars: + APP_NAME: "buildtest{{exeExt}}" + + +tasks: + + build: + summary: Builds the application + cmds: + - go build -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + + package: + summary: Packages a production build of the application into a `.app` or `.exe` bundle + cmds: + - task: package:darwin + - task: package:windows + +# ------------------------------------------------------------------------------ + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails3 generate icons -input appicon.png + + build:production:darwin: + summary: Creates a production build of the application + cmds: + - GOOS=darwin GOARCH={{.GOARCH}} go build -tags production -ldflags="-w -s" -o build/bin/{{.APP_NAME}} + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + MACOSX_DEPLOYMENT_TARGET: "10.13" + GOARCH: '{{.GOARCH}}' + + generate:app_bundle: + summary: Builds a `.app` bundle + cmds: + - mkdir -p {{.APP_NAME}}.app/Contents/{MacOS,Resources} + - cp build/icons.icns {{.APP_NAME}}.app/Contents/Resources + - cp build/bin/{{.APP_NAME}} {{.APP_NAME}}.app/Contents/MacOS + - cp build/Info.plist {{.APP_NAME}}.app/Contents + + package:darwin:arm64: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + vars: + ARCH: arm64 + - generate:icons + cmds: + - task: generate:app_bundle + + package:darwin: + summary: Packages a production build of the application into a `.app` bundle + platforms: + - darwin + deps: + - task: build:production:darwin + - generate:icons + cmds: + - task: generate:app_bundle + + generate:syso: + dir: build + platforms: + - windows + cmds: + - wails3 generate syso -arch {{.GOARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json + vars: + GOARCH: '{{.GOARCH}}' + + package:windows: + summary: Packages a production build of the application into a `.exe` bundle + platforms: + - windows/amd64 + deps: + - generate:icons + cmds: + - task: generate:syso + vars: + GOARCH: amd64 + - go build -tags production -gcflags=all="-N -l" -o bin/{{.APP_NAME}} + - powershell Remove-item *.syso + + + package:windows:arm64: + summary: Packages a production build of the application into a `.exe` bundle (ARM64) + platforms: + - windows + cmds: + - task: generate:syso + vars: + GOARCH: arm64 + - go build -tags production -ldflags="-w -s -H windowsgui" -o bin/buildtest.arm64.exe + - powershell Remove-item *.syso + env: + GOARCH: arm64 diff --git a/v3/examples/dialogs/build/Info.dev.plist b/v3/examples/dialogs/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/dialogs/build/Info.dev.plist @@ -0,0 +1,35 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + + + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/Info.plist b/v3/examples/dialogs/build/Info.plist new file mode 100644 index 000000000..7f03f54e9 --- /dev/null +++ b/v3/examples/dialogs/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + app + CFBundleIdentifier + com.wails.app + CFBundleVersion + v1.0.0 + CFBundleGetInfoString + The ultimate thing + CFBundleShortVersionString + v1 + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + (c) Me + + \ No newline at end of file diff --git a/v3/examples/dialogs/build/appicon.png b/v3/examples/dialogs/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/dialogs/build/appicon.png differ diff --git a/v3/examples/dialogs/build/icon.ico b/v3/examples/dialogs/build/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/dialogs/build/icon.ico differ diff --git a/v3/examples/dialogs/build/icons.icns b/v3/examples/dialogs/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/dialogs/build/icons.icns differ diff --git a/v3/examples/dialogs/build/info.json b/v3/examples/dialogs/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/dialogs/build/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "v1.0.0" + }, + "info": { + "0000": { + "ProductVersion": "v1.0.0", + "CompanyName": "My Company Name", + "FileDescription": "A thing that does a thing", + "LegalCopyright": "(c) 2023 My Company Name", + "ProductName": "My Product Name", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/v3/examples/dialogs/build/wails.exe.manifest b/v3/examples/dialogs/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/dialogs/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/dialogs/main.go b/v3/examples/dialogs/main.go new file mode 100644 index 000000000..07521b30d --- /dev/null +++ b/v3/examples/dialogs/main.go @@ -0,0 +1,362 @@ +package main + +import ( + _ "embed" + "log" + "log/slog" + "os" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Dialogs Demo", + Description: "A demo of the dialogs API", + Assets: application.AlphaAssets, + Logger: application.DefaultLogger(slog.LevelDebug), + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.ServicesMenu) + menu.AddRole(application.HelpMenu) + + // Let's make a "Demo" menu + infoMenu := menu.AddSubmenu("Info") + infoMenu.Add("Info").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + + infoMenu.Add("Info (Title only)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.InfoDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationDarkMode256) + dialog.Show() + }) + infoMenu.Add("About").OnClick(func(ctx *application.Context) { + app.Menu.ShowAbout() + }) + + questionMenu := menu.AddSubmenu("Question") + questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (Attached to Window)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.AttachToWindow(app.Window.Current()) + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (With Default)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetTitle("Quit") + dialog.SetMessage("You have unsaved work. Are you sure you want to quit?") + dialog.AddButton("Yes").OnClick(func() { + app.Quit() + }) + no := dialog.AddButton("No") + dialog.SetDefaultButton(no) + dialog.Show() + }) + questionMenu.Add("Question (With Cancel)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog(). + SetTitle("Update"). + SetMessage("The cancel button is selected when pressing escape") + download := dialog.AddButton("๐Ÿ“ฅ Download") + download.OnClick(func() { + application.InfoDialog().SetMessage("Downloading...").Show() + }) + no := dialog.AddButton("Cancel") + dialog.SetDefaultButton(download) + dialog.SetCancelButton(no) + dialog.Show() + }) + questionMenu.Add("Question (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.QuestionDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhiteTransparent) + likeIt := dialog.AddButton("I like it!").OnClick(func() { + application.InfoDialog().SetMessage("Thanks!").Show() + }) + dialog.AddButton("Not so keen...").OnClick(func() { + application.InfoDialog().SetMessage("Too bad!").Show() + }) + dialog.SetDefaultButton(likeIt) + dialog.Show() + }) + + warningMenu := menu.AddSubmenu("Warning") + warningMenu.Add("Warning").OnClick(func(ctx *application.Context) { + application.WarningDialog(). + SetTitle("Custom Title"). + SetMessage("This is a custom message"). + Show() + }) + warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.WarningDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.ApplicationLightMode256) + dialog.Show() + }) + + errorMenu := menu.AddSubmenu("Error") + errorMenu.Add("Error").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Ooops") + dialog.SetMessage("I accidentally the whole of Twitter") + dialog.Show() + }) + errorMenu.Add("Error (Title Only)").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) { + application.ErrorDialog(). + SetMessage("This is a custom message"). + Show() + }) + errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := application.ErrorDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(icons.WailsLogoWhite) + dialog.Show() + }) + + openMenu := menu.AddSubmenu("Open") + openMenu.Add("Open File").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + AttachToWindow(app.Window.Current()). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForMultipleSelection() + if len(result) > 0 { + application.InfoDialog().SetMessage(strings.Join(result, ",")).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) { + result, _ := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + CanChooseFiles(false). + ResolvesAliases(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) { + dialog := application.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + ResolvesAliases(true) + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file/directory") + } else { + dialog.SetTitle("Select a file/directory") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file/directory selected").Show() + } + }) + openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) { + cwd, _ := os.Getwd() + dialog := application.OpenFileDialog(). + SetTitle("Select a file"). + SetMessage("Select a file to open"). + SetButtonText("Let's do this!"). + SetDirectory(cwd). + CanCreateDirectories(true). + ResolvesAliases(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + CanSelectHiddenExtension(true). + AddFilter("Text Files", "*.txt; *.md"). + AddFilter("Video Files", "*.mov; *.mp4; *.avi") + + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file") + } else { + dialog.SetTitle("Select a file") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } else { + application.InfoDialog().SetMessage("No file selected").Show() + } + }) + + saveMenu := menu.AddSubmenu("Save") + saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + AttachToWindow(app.Window.Current()). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + CanCreateDirectories(false). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) { + result, _ := application.SaveFileDialog(). + CanCreateDirectories(false). + ShowHiddenFiles(true). + SetMessage("Select a file"). + SetDirectory("/Applications"). + SetButtonText("Let's do this!"). + SetFilename("README.md"). + HideExtension(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + application.InfoDialog().SetMessage(result).Show() + } + }) + + app.Menu.Set(menu) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/drag-n-drop/README.md b/v3/examples/drag-n-drop/README.md new file mode 100644 index 000000000..cb3c56fd0 --- /dev/null +++ b/v3/examples/drag-n-drop/README.md @@ -0,0 +1,27 @@ +# Drag-n-drop Example + +This example demonstrates how to handle files being dragged into the application. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +# Status + +| Platform | Status | +|----------|-------------| +| Mac | Working | +| Windows | Not Working | +| Linux | | diff --git a/v3/examples/drag-n-drop/assets/index.html b/v3/examples/drag-n-drop/assets/index.html new file mode 100644 index 000000000..f1e5595ca --- /dev/null +++ b/v3/examples/drag-n-drop/assets/index.html @@ -0,0 +1,38 @@ + + + + + Title + + + +

Drag-n-drop Demo

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

Events Demo

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

Wails + Javascript

+
+
Please enter your name below ๐Ÿ‘‡
+
+ + +
+
+ +
+ + + diff --git a/v3/examples/file-association/frontend/main.js b/v3/examples/file-association/frontend/main.js new file mode 100644 index 000000000..c24b3b1ef --- /dev/null +++ b/v3/examples/file-association/frontend/main.js @@ -0,0 +1,21 @@ +import {GreetService} from "./bindings/changeme"; +import {Events} from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/examples/file-association/frontend/package-lock.json b/v3/examples/file-association/frontend/package-lock.json new file mode 100644 index 000000000..9ba47fffa --- /dev/null +++ b/v3/examples/file-association/frontend/package-lock.json @@ -0,0 +1,860 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "devDependencies": { + "@wailsio/runtime": "latest", + "vite": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz", + "integrity": "sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.3.tgz", + "integrity": "sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.3.tgz", + "integrity": "sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.3.tgz", + "integrity": "sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.3.tgz", + "integrity": "sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.3.tgz", + "integrity": "sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.3.tgz", + "integrity": "sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.3.tgz", + "integrity": "sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.3.tgz", + "integrity": "sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.3.tgz", + "integrity": "sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.3.tgz", + "integrity": "sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.3.tgz", + "integrity": "sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.3.tgz", + "integrity": "sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.3.tgz", + "integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.3.tgz", + "integrity": "sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.3.tgz", + "integrity": "sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.3.tgz", + "integrity": "sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.3.tgz", + "integrity": "sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.28.tgz", + "integrity": "sha512-caMnAcKxxDrIWYgCZAMY2kdL++X4ehO2+JvH5na21xfDqz3VnHkEjxsH3jfhgd34M8LY80QEH8iqoMYytDFE/g==", + "dev": true, + "dependencies": { + "nanoid": "^5.0.7" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", + "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/rollup": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", + "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.3", + "@rollup/rollup-android-arm64": "4.24.3", + "@rollup/rollup-darwin-arm64": "4.24.3", + "@rollup/rollup-darwin-x64": "4.24.3", + "@rollup/rollup-freebsd-arm64": "4.24.3", + "@rollup/rollup-freebsd-x64": "4.24.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.3", + "@rollup/rollup-linux-arm-musleabihf": "4.24.3", + "@rollup/rollup-linux-arm64-gnu": "4.24.3", + "@rollup/rollup-linux-arm64-musl": "4.24.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.3", + "@rollup/rollup-linux-riscv64-gnu": "4.24.3", + "@rollup/rollup-linux-s390x-gnu": "4.24.3", + "@rollup/rollup-linux-x64-gnu": "4.24.3", + "@rollup/rollup-linux-x64-musl": "4.24.3", + "@rollup/rollup-win32-arm64-msvc": "4.24.3", + "@rollup/rollup-win32-ia32-msvc": "4.24.3", + "@rollup/rollup-win32-x64-msvc": "4.24.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/v3/examples/file-association/frontend/package.json b/v3/examples/file-association/frontend/package.json new file mode 100644 index 000000000..2642d7a41 --- /dev/null +++ b/v3/examples/file-association/frontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build --minify false --mode development", + "build:prod": "vite build --mode production", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.0.0", + "@wailsio/runtime": "latest" + } +} \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/Inter-Medium.ttf b/v3/examples/file-association/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/examples/file-association/frontend/public/Inter-Medium.ttf differ diff --git a/v3/examples/file-association/frontend/public/javascript.svg b/v3/examples/file-association/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/examples/file-association/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/style.css b/v3/examples/file-association/frontend/public/style.css new file mode 100644 index 000000000..259397254 --- /dev/null +++ b/v3/examples/file-association/frontend/public/style.css @@ -0,0 +1,160 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/examples/file-association/frontend/public/wails.png b/v3/examples/file-association/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/examples/file-association/frontend/public/wails.png differ diff --git a/v3/examples/file-association/go.mod b/v3/examples/file-association/go.mod new file mode 100644 index 000000000..4d7fef32e --- /dev/null +++ b/v3/examples/file-association/go.mod @@ -0,0 +1,50 @@ +module changeme + +go 1.24.0 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.7 + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/file-association/go.sum b/v3/examples/file-association/go.sum new file mode 100644 index 000000000..cbadfe003 --- /dev/null +++ b/v3/examples/file-association/go.sum @@ -0,0 +1,146 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/file-association/greetservice.go b/v3/examples/file-association/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/examples/file-association/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/examples/file-association/main.go b/v3/examples/file-association/main.go new file mode 100644 index 000000000..9fb6fa058 --- /dev/null +++ b/v3/examples/file-association/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "embed" + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed frontend +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "fileassoc", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + FileAssociations: []string{".wails"}, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + var filename string + app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(event *application.ApplicationEvent) { + filename = event.Context().Filename() + }) + + window.OnWindowEvent(events.Common.WindowShow, func(event *application.WindowEvent) { + application.InfoDialog(). + SetTitle("File Opened"). + SetMessage("Application opened with file: " + filename). + Show() + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + case <-app.Context().Done(): + return + } + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/file-association/test.wails b/v3/examples/file-association/test.wails new file mode 100644 index 000000000..dde58d5e8 --- /dev/null +++ b/v3/examples/file-association/test.wails @@ -0,0 +1 @@ +Once the application is built and installed, double click on this file to open the application. \ No newline at end of file diff --git a/v3/examples/frameless/README.md b/v3/examples/frameless/README.md new file mode 100644 index 000000000..f17f7a5d3 --- /dev/null +++ b/v3/examples/frameless/README.md @@ -0,0 +1,19 @@ +# Frameless Example + +This example is a demonstration of using frameless windows in Wails. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/frameless/assets/index.html b/v3/examples/frameless/assets/index.html new file mode 100644 index 000000000..2776cfaa9 --- /dev/null +++ b/v3/examples/frameless/assets/index.html @@ -0,0 +1,53 @@ + + + + + + + + +
+
Draggable
+
Not Draggable
+
Not Draggable
+
Not Draggable
+
Draggable
+
+ + diff --git a/v3/examples/frameless/main.go b/v3/examples/frameless/main.go new file mode 100644 index 000000000..93be412fd --- /dev/null +++ b/v3/examples/frameless/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Frameless Demo", + Description: "A demo of frameless windows", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/gin-example/README.md b/v3/examples/gin-example/README.md new file mode 100644 index 000000000..b4d85cb9b --- /dev/null +++ b/v3/examples/gin-example/README.md @@ -0,0 +1,104 @@ +# Gin Example + +This example demonstrates how to use the [Gin web framework](https://github.com/gin-gonic/gin) with Wails. + +## Overview + +This example shows how to: + +- Set up Gin as the asset handler for a Wails application +- Create a middleware that routes requests between Wails and Gin +- Define API endpoints with Gin +- Communicate between the Gin-served frontend and Wails backend +- Implement custom Gin middleware + +## Running the Example + +```bash +cd v3/examples/gin-example +go mod tidy +go run . +``` + +## How It Works + +The example uses Gin's HTTP router to serve the frontend content whilst still allowing Wails to handle its internal routes. This is achieved through: + +1. Creating a Gin router with routes for the frontend +2. Implementing a middleware function that decides whether to pass requests to Gin or let Wails handle them +3. Configuring the Wails application to use both the Gin router as the asset handler and the custom middleware + +### Wails-Gin Integration + +The key part of the integration is the middleware function: + +```go +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle its internal routes + if r.URL.Path == "/wails/runtime.js" || r.URL.Path == "/wails/ipc" { + next.ServeHTTP(w, r) + return + } + + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} +``` + +This allows you to leverage Gin's powerful routing and middleware capabilities whilst still maintaining full access to Wails features. + +### Custom Gin Middleware + +The example also demonstrates how to create custom Gin middleware: + +```go +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} +``` + +This middleware is applied to all Gin routes and logs details about each request. + +### Application Configuration + +The Wails application is configured to use Gin as follows: + +```go +app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, +}) +``` + +This configuration tells Wails to: +1. Use the Gin engine as the primary handler for HTTP requests +2. Use our custom middleware to route requests between Wails and Gin diff --git a/v3/examples/gin-example/go.mod b/v3/examples/gin-example/go.mod new file mode 100644 index 000000000..ef253a93c --- /dev/null +++ b/v3/examples/gin-example/go.mod @@ -0,0 +1,72 @@ +module gin-example + +go 1.24.0 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/wailsapp/wails/v3 v3.0.0 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/gin-example/go.sum b/v3/examples/gin-example/go.sum new file mode 100644 index 000000000..59438b668 --- /dev/null +++ b/v3/examples/gin-example/go.sum @@ -0,0 +1,203 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/v3/examples/gin-example/main.go b/v3/examples/gin-example/main.go new file mode 100644 index 000000000..8138d5403 --- /dev/null +++ b/v3/examples/gin-example/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "embed" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed static +var staticFiles embed.FS + +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} + +func main() { + // Create a new Gin router + ginEngine := gin.New() // Using New() instead of Default() to add our own middleware + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + // Serve embedded static files + ginEngine.StaticFS("/static", http.FS(staticFiles)) + + // Define routes + ginEngine.GET("/", func(c *gin.Context) { + file, err := staticFiles.ReadFile("static/index.html") + if err != nil { + c.String(http.StatusInternalServerError, "Error reading index.html") + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", file) + }) + + ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Create a new Wails application + app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, + }) + + // Register event handler and store cleanup function + removeGinHandler := app.Event.On("gin-button-clicked", func(event *application.CustomEvent) { + log.Printf("Received event from frontend: %v", event.Data) + }) + // Note: In production, call removeGinHandler() during cleanup + _ = removeGinHandler + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", + }) + + // Run the app + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/gin-example/static/index.html b/v3/examples/gin-example/static/index.html new file mode 100644 index 000000000..df02b840f --- /dev/null +++ b/v3/examples/gin-example/static/index.html @@ -0,0 +1,96 @@ + + + Wails + Gin Example + + + +
+

Wails + Gin Integration

+
+

Hello World!

+

This page is being served by Gin.

+

Click the button below to trigger a Wails event:

+ + +
+
+

API Example

+

Try the Gin API endpoint:

+ +
+
+
+ + + diff --git a/v3/examples/gin-routing/README.md b/v3/examples/gin-routing/README.md new file mode 100644 index 000000000..5d8babc31 --- /dev/null +++ b/v3/examples/gin-routing/README.md @@ -0,0 +1,26 @@ +# Gin Routing Example + +This example demonstrates how to use the [Gin web framework](https://github.com/gin-gonic/gin) as a router with Wails. + +## Overview + +This example shows how to: + +- Set up Gin as the asset handler for a Wails application +- Create a middleware that routes requests between Wails and Gin +- Define API endpoints with Gin +- Communicate between the Gin-served frontend and Wails backend +- Implement custom Gin middleware + +## Running the Example + +```bash +cd v3/examples/gin-routing +go mod tidy +go run . +``` + +## Documentation + +Please consult the [Using Gin for Routing](https://v3.wails.io/guides/gin-routing/) guide for a detailed explanation of the example. + diff --git a/v3/examples/gin-routing/go.mod b/v3/examples/gin-routing/go.mod new file mode 100644 index 000000000..ef253a93c --- /dev/null +++ b/v3/examples/gin-routing/go.mod @@ -0,0 +1,72 @@ +module gin-example + +go 1.24.0 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/wailsapp/wails/v3 v3.0.0 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/gin-routing/go.sum b/v3/examples/gin-routing/go.sum new file mode 100644 index 000000000..59438b668 --- /dev/null +++ b/v3/examples/gin-routing/go.sum @@ -0,0 +1,203 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/v3/examples/gin-routing/main.go b/v3/examples/gin-routing/main.go new file mode 100644 index 000000000..a78494808 --- /dev/null +++ b/v3/examples/gin-routing/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "embed" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed static +var staticFiles embed.FS + +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} + +func main() { + // Create a new Gin router + ginEngine := gin.New() // Using New() instead of Default() to add our own middleware + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + // Serve embedded static files + ginEngine.StaticFS("/static", http.FS(staticFiles)) + + // Define routes + ginEngine.GET("/", func(c *gin.Context) { + file, err := staticFiles.ReadFile("static/index.html") + if err != nil { + c.String(http.StatusInternalServerError, "Error reading index.html") + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", file) + }) + + ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Create a new Wails application + app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, + }) + + // Register event handler + app.Event.On("gin-button-clicked", func(event *application.CustomEvent) { + log.Printf("Received event from frontend: %v", event.Data) + }) + + // Create window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", + }) + + // Run the app + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/gin-routing/static/index.html b/v3/examples/gin-routing/static/index.html new file mode 100644 index 000000000..aefbd7a28 --- /dev/null +++ b/v3/examples/gin-routing/static/index.html @@ -0,0 +1,94 @@ + + + Wails + Gin Example + + + +
+

Wails + Gin Integration

+
+

Hello World!

+

This page is being served by Gin.

+

Click the button below to trigger a Wails event:

+ + +
+
+

API Example

+

Try the Gin API endpoint:

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

Gin Service Example

+ +
+

API Endpoints

+

Try the Gin API endpoints mounted at /api:

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

Event Communication

+

Trigger an event to communicate with the Gin service:

+ + + +
+
Event responses will appear here...
+
+
+ + + + + + diff --git a/v3/examples/gin-service/go.mod b/v3/examples/gin-service/go.mod new file mode 100644 index 000000000..607c1701f --- /dev/null +++ b/v3/examples/gin-service/go.mod @@ -0,0 +1,74 @@ +module gin-service + +go 1.24.0 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/wailsapp/wails/v3 v3.0.0-alpha.9 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/wailsapp/wails/v3 v3.0.0-alpha.9 => ../.. diff --git a/v3/examples/gin-service/go.sum b/v3/examples/gin-service/go.sum new file mode 100644 index 000000000..86b344c16 --- /dev/null +++ b/v3/examples/gin-service/go.sum @@ -0,0 +1,208 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/v3/examples/gin-service/main.go b/v3/examples/gin-service/main.go new file mode 100644 index 000000000..f27fef415 --- /dev/null +++ b/v3/examples/gin-service/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "embed" + "log/slog" + "os" + + "gin-service/services" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "Gin Service Demo", + Description: "A demo of using Gin in Wails services", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{ + Route: "/api", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Gin Service Demo", + Width: 1024, + Height: 768, + }) + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/gin-service/services/gin_service.go b/v3/examples/gin-service/services/gin_service.go new file mode 100644 index 000000000..e0fa290ec --- /dev/null +++ b/v3/examples/gin-service/services/gin_service.go @@ -0,0 +1,250 @@ +package services + +import ( + "context" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// User represents a user in the system +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` +} + +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + users []User + nextID int + mu sync.RWMutex + app *application.App + maxUsers int // Maximum number of users to prevent unbounded growth + removeEventHandler func() // Store cleanup function for event handler +} + +type EventData struct { + Message string `json:"message"` + Timestamp string `json:"timestamp"` +} + +// NewGinService creates a new GinService instance +func NewGinService() *GinService { + // Create a new Gin router + ginEngine := gin.New() + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + service := &GinService{ + ginEngine: ginEngine, + users: []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)}, + {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)}, + {ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)}, + }, + nextID: 4, + maxUsers: 1000, // Limit to prevent unbounded slice growth + } + + // Define routes + service.setupRoutes() + + return service +} + +// ServiceName returns the name of the service +func (s *GinService) ServiceName() string { + return "Gin API Service" +} + +// ServiceStartup is called when the service starts +func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // You can access the application instance via ctx + s.app = application.Get() + + // Register an event handler that can be triggered from the frontend + // Store the cleanup function for proper resource management + s.removeEventHandler = s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + // Parse the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // You could also emit an event back to the frontend + s.app.Event.Emit("gin-api-response", map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }) + }) + + return nil +} + +// ServiceShutdown is called when the service shuts down +func (s *GinService) ServiceShutdown(ctx context.Context) error { + // Clean up event handler to prevent memory leaks + if s.removeEventHandler != nil { + s.removeEventHandler() + } + return nil +} + +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // All other requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} + +// setupRoutes configures the API routes +func (s *GinService) setupRoutes() { + // Basic info endpoint + s.ginEngine.GET("/info", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Gin API Service", + "version": "1.0.0", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Users group + users := s.ginEngine.Group("/users") + { + // Get all users + users.GET("", func(c *gin.Context) { + s.mu.RLock() + defer s.mu.RUnlock() + c.JSON(http.StatusOK, s.users) + }) + + // Get user by ID + users.GET("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.RLock() + defer s.mu.RUnlock() + + for _, user := range s.users { + if user.ID == id { + c.JSON(http.StatusOK, user) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + + // Create a new user + users.POST("", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Validate required fields + if newUser.Name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Name is required"}) + return + } + if newUser.Email == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Email is required"}) + return + } + // Basic email validation (consider using a proper validator library in production) + if !strings.Contains(newUser.Email, "@") { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email format"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + // Check if we've reached the maximum number of users + if len(s.users) >= s.maxUsers { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Maximum number of users reached"}) + return + } + + // Set the ID and creation time + newUser.ID = s.nextID + newUser.CreatedAt = time.Now() + s.nextID++ + + // Add to the users slice + s.users = append(s.users, newUser) + + c.JSON(http.StatusCreated, newUser) + + // Emit an event to notify about the new user + s.app.Event.Emit("user-created", newUser) + }) + + // Delete a user + users.DELETE("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + for i, user := range s.users { + if user.ID == id { + // Remove the user from the slice + s.users = append(s.users[:i], s.users[i+1:]...) + c.JSON(http.StatusOK, gin.H{"message": "User deleted"}) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + } +} + +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Log request details + statusCode := c.Writer.Status() + clientIP := c.ClientIP() + method := c.Request.Method + path := c.Request.URL.Path + + // Get the application instance + app := application.Get() + if app != nil { + app.Logger.Info("HTTP Request", + "status", statusCode, + "method", method, + "path", path, + "ip", clientIP, + "latency", latency, + ) + } + } +} diff --git a/v3/examples/hide-window/main.go b/v3/examples/hide-window/main.go new file mode 100644 index 000000000..1a65c7498 --- /dev/null +++ b/v3/examples/hide-window/main.go @@ -0,0 +1,69 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" +) + +func main() { + app := application.New(application.Options{ + Name: "Hide Window Demo", + Description: "A test of Hidden window and display it", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 800, + Frameless: false, + AlwaysOnTop: false, + Hidden: false, + DisableResize: false, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + window.Hide() + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + // Click Dock icon tigger application show + app.Event.OnApplicationEvent(events.Mac.ApplicationShouldHandleReopen, func(event *application.ApplicationEvent) { + println("reopen") + window.Show() + }) + + myMenu := app.NewMenu() + myMenu.Add("Show").OnClick(func(ctx *application.Context) { + window.Show() + }) + + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + systemTray.OnClick(func() { + window.Show() + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/html-dnd-api/README.md b/v3/examples/html-dnd-api/README.md new file mode 100644 index 000000000..1b06f3882 --- /dev/null +++ b/v3/examples/html-dnd-api/README.md @@ -0,0 +1,43 @@ +# HTML Drag and Drop API Example + +This example should demonstrate whether the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API") works correctly. + +## Expected Behaviour + +When dragging the "draggable", in the console should be printed: +1. "dragstart" once +2. "drag" many times +3. "dragend" once + +When dragging the "draggable" on the drop target, the inner text of the latter shoud change and in the console should be printed: +1. "dragstart" once +2. "drag" many times +3. "dragenter" once +4. "dragover" many times (alternating with "drag") +5. - "drop" once (in case of a drop inside the drop target) + - "dragleave" once (in case the draggable div leaves the drop target) +6. "dragend" once + +## Running the example + +To run the example, simply run the following command: + +```bash +go run main.go +``` + +## Building the example + +To build the example in debug mode, simply run the following command: + +```bash +wails3 task build +``` + +# Status + +| Platform | Status | +|----------|-------------| +| Mac | Working | +| Windows | Not Working | +| Linux | | diff --git a/v3/examples/html-dnd-api/assets/index.html b/v3/examples/html-dnd-api/assets/index.html new file mode 100644 index 000000000..a64b9378a --- /dev/null +++ b/v3/examples/html-dnd-api/assets/index.html @@ -0,0 +1,75 @@ + + + + + Title + + + +

HTML Drag and Drop API Demo

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

Notifications are now authorized.

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

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

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

Notifications are authorized.

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

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

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

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

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

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

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

Wails + Typescript + Desktop Notifications

+

Send notifications ๐Ÿ‘‡

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

Wails + Typescript + Desktop Notifications

+

Send notifications ๐Ÿ‘‡

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

Notifications are now authorized.

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

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

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

Notifications are authorized.

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

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

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

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

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

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

"; + console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n"); + } + } catch (error) { + console.error(error); + } +}); + +const unlisten = Events.On("notification:action", (response) => { + console.info(`Recieved a ${response.name} event`); + const { userInfo, ...base } = response.data[0]; + console.info("Notification Response:"); + console.table(base); + console.info("Notification Response Metadata:"); + console.table(userInfo); + const table = ` +
Notification Response
+ + + ${Object.keys(base).map(key => ``).join("")} + + + ${Object.values(base).map(value => ``).join("")} + +
${key}
${value}
+
Notification Metadata
+ + + ${Object.keys(userInfo).map(key => ``).join("")} + + + ${Object.values(userInfo).map(value => ``).join("")} + +
${key}
${value}
+ `; + const footer = document.querySelector("#response"); + if (footer) footer.innerHTML = table; +}); + +window.onbeforeunload = () => unlisten(); \ No newline at end of file diff --git a/v3/examples/notifications/frontend/src/vite-env.d.ts b/v3/examples/notifications/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/examples/notifications/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/examples/notifications/frontend/tsconfig.json b/v3/examples/notifications/frontend/tsconfig.json new file mode 100644 index 000000000..c267ecf24 --- /dev/null +++ b/v3/examples/notifications/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/examples/notifications/go.mod b/v3/examples/notifications/go.mod new file mode 100644 index 000000000..ebd5f4fc0 --- /dev/null +++ b/v3/examples/notifications/go.mod @@ -0,0 +1,53 @@ +module notifications + +go 1.24.0 + +toolchain go1.24.1 + +require github.com/wailsapp/wails/v3 v3.0.0-dev + +require ( + dario.cat/mergo v1.0.1 // indirect + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. diff --git a/v3/examples/notifications/go.sum b/v3/examples/notifications/go.sum new file mode 100644 index 000000000..6f8b2a29a --- /dev/null +++ b/v3/examples/notifications/go.sum @@ -0,0 +1,148 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/examples/notifications/main.go b/v3/examples/notifications/main.go new file mode 100644 index 000000000..c0e006652 --- /dev/null +++ b/v3/examples/notifications/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + _ "embed" + "fmt" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/notifications" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + // Create a new Notification Service + ns := notifications.New() + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "Notifications Demo", + Description: "A demo of using desktop notifications with Wails", + Services: []application.Service{ + application.NewService(ns), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Name: "main", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Pass a notification callback that will be called when a notification is actioned. + ns.OnNotificationResponse(func(result notifications.NotificationResult) { + if result.Error != nil { + println(fmt.Errorf("parsing notification result failed: %s", result.Error)) + } else { + fmt.Printf("Response: %+v\n", result.Response) + println("Sending response to frontend...") + app.Event.Emit("notification:action", result.Response) + } + }) + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/panic-handling/README.md b/v3/examples/panic-handling/README.md new file mode 100644 index 000000000..99068495f --- /dev/null +++ b/v3/examples/panic-handling/README.md @@ -0,0 +1,11 @@ +# Panic Handling Example + +This example is a demonstration of how to handle panics in your application. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` diff --git a/v3/examples/panic-handling/assets/index.html b/v3/examples/panic-handling/assets/index.html new file mode 100644 index 000000000..f4b5fe886 --- /dev/null +++ b/v3/examples/panic-handling/assets/index.html @@ -0,0 +1,27 @@ + + + + + + Window Call Demo + + + + + + + + + \ No newline at end of file diff --git a/v3/examples/panic-handling/main.go b/v3/examples/panic-handling/main.go new file mode 100644 index 000000000..cb19165be --- /dev/null +++ b/v3/examples/panic-handling/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "embed" + "fmt" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +//go:embed assets/* +var assets embed.FS + +type WindowService struct{} + +func (s *WindowService) GeneratePanic() { + s.call1() +} + +func (s *WindowService) call1() { + s.call2() +} + +func (s *WindowService) call2() { + panic("oh no! something went wrong deep in my service! :(") +} + +// ============================================== + +func main() { + app := application.New(application.Options{ + Name: "Panic Handler Demo", + Description: "A demo of Handling Panics", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + Services: []application.Service{ + application.NewService(&WindowService{}), + }, + PanicHandler: func(panicDetails *application.PanicDetails) { + fmt.Printf("*** There was a panic! ***\n") + fmt.Printf("Time: %s\n", panicDetails.Time) + fmt.Printf("Error: %s\n", panicDetails.Error) + fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) + application.InfoDialog().SetMessage("There was a panic!").Show() + }, + }) + + app.Window.New(). + SetTitle("WebviewWindow 1"). + Show() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/plain/README.md b/v3/examples/plain/README.md new file mode 100644 index 000000000..9d44f5119 --- /dev/null +++ b/v3/examples/plain/README.md @@ -0,0 +1,19 @@ +# Plain Example + +This example is a demonstration of different ways to create applications without using npm. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/plain/main.go b/v3/examples/plain/main.go new file mode 100644 index 000000000..3132d65b8 --- /dev/null +++ b/v3/examples/plain/main.go @@ -0,0 +1,84 @@ +package main + +import ( + _ "embed" + "log" + "net/http" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Plain", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`Plain Bundle

Plain Bundle

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



Clicking this paragraph emits an event...

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

AWESOME!

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

Raw Message Demo

+
+To send a raw message from this window, enter some text and click the button: +
+
+ + + + diff --git a/v3/examples/raw-message/main.go b/v3/examples/raw-message/main.go new file mode 100644 index 000000000..cd879ea5c --- /dev/null +++ b/v3/examples/raw-message/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "embed" + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Raw Message Demo", + Description: "A demo of sending raw messages from the frontend", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + RawMessageHandler: func(window application.Window, message string) { + println("Raw message received from Window '" + window.Name() + "' with message: " + message) + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Name: "Window 1", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/screen/README.md b/v3/examples/screen/README.md new file mode 100644 index 000000000..1f922f341 --- /dev/null +++ b/v3/examples/screen/README.md @@ -0,0 +1,19 @@ +# Screen Example + +This example will detect all attached screens and display their details. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/screen/assets/examples.js b/v3/examples/screen/assets/examples.js new file mode 100644 index 000000000..8abe3f0ca --- /dev/null +++ b/v3/examples/screen/assets/examples.js @@ -0,0 +1,206 @@ +window.examples = [ + [ + // Normal examples (demonstrate real life scenarios) + { + name: "Single 4k monitor", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + ] + }, + { + name: "Two monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + ] + }, + { + name: "Two monitors (2)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1, name: `23" FHD 96DPI`}, + {id: 2, w: 1920, h: 1080, s: 1.25, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI (125%)`}, + ] + }, + { + name: "Three monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: {id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + ] + }, + { + name: "Four monitors", + screens: [ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: {id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: {id: 2, align: "b", offset: 0}, name: `23" FHD 96DPI (125%)`}, + {id: 4, w: 1080, h: 1920, s: 1, parent: {id: 1, align: "l", offset: 0}, name: `23" FHD (90deg)`}, + ] + }, + ], + [ + // Test cases examples (demonstrate the algorithm basics) + { + name: "Child scaled, Start offset", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: {id: 1, align: "r", offset: 600}, name: "Child"}, + ] + }, + { + name: "Child scaled, End offset", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: {id: 1, align: "r", offset: -600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, Start offset percent", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: 600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, End offset percent", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: -600}, name: "Child"}, + ] + }, + { + name: "Parent scaled, Start align", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1100, s: 1, parent: {id: 1, align: "r", offset: 0}, name: "Child"}, + ] + }, + { + name: "Parent scaled, End align", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: {id: 1, align: "r", offset: 0}, name: "Child"}, + ] + }, + { + name: "Parent scaled, in-between", + screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1500, s: 1, parent: {id: 1, align: "r", offset: -200}, name: "Child"}, + ] + }, + ], + [ + // Edge cases examples + { + name: "Parent order (5 is parent of 4)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 600, s: 1.25, parent: {id: 1, align: "r", offset: -200}}, + {id: 3, w: 800, h: 800, s: 1.25, parent: {id: 2, align: "b", offset: 0}}, + {id: 4, w: 800, h: 1080, s: 1.5, parent: {id: 2, align: "re", offset: 100}}, + {id: 5, w: 600, h: 600, s: 1, parent: {id: 3, align: "r", offset: 100}}, + ] + }, + { + name: "de-intersection reparent", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1680, h: 1050, s: 1.25, parent: {id: 1, align: "r", offset: 10}}, + {id: 3, w: 1440, h: 900, s: 1.5, parent: {id: 1, align: "le", offset: 150}}, + {id: 4, w: 1024, h: 768, s: 1, parent: {id: 3, align: "bc", offset: -200}}, + {id: 5, w: 1024, h: 768, s: 1.25, parent: {id: 4, align: "r", offset: 400}}, + ] + }, + { + name: "de-intersection (unattached child)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1.5, parent: {id: 1, align: "le", offset: 10}}, + {id: 3, w: 1024, h: 768, s: 1.25, parent: {id: 2, align: "b", offset: 100}}, + {id: 4, w: 1024, h: 768, s: 1, parent: {id: 3, align: "r", offset: 500}}, + ] + }, + { + name: "Multiple de-intersection", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: {id: 1, align: "be", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: {id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: {id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: {id: 4, align: "be", offset: 100}}, + ] + }, + { + name: "Multiple de-intersection (left-side)", + screens: [ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: {id: 1, align: "le", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: {id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: {id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: {id: 4, align: "be", offset: 100}}, + ] + }, + { + name: "Parent de-intersection child offset", + screens: [ + {id: 1, w: 1600, h: 1600, s: 1.5}, + {id: 2, w: 800, h: 800, s: 1, parent: {id: 1, align: "r", offset: 0}}, + {id: 3, w: 800, h: 800, s: 1, parent: {id: 1, align: "r", offset: 800}}, + {id: 4, w: 800, h: 1600, s: 1, parent: {id: 2, align: "r", offset: 0}}, + ] + }, + ], +].map(sections => sections.map(layout => { + return parseLayout(layout) +})) + +function parseLayout(layout) { + const screens = [] + + for (const screen of layout.screens) { + let x = 0, y = 0 + const {w, h} = screen + + if (screen.parent) { + const parent = screens.find(s => s.ID == screen.parent.id).Bounds + const offset = screen.parent.offset + let align = screen.parent.align + let align2 = "" + + if (align.length == 2) { + align2 = align.charAt(1) + align = align.charAt(0) + } + + x = parent.X + y = parent.Y + // t: top, b: bottom, l: left, r: right, e: edge, c: corner + if (align == "t" || align == "b") { + x += offset + (align2 == "e" || align2 == "c" ? parent.Width : 0) - (align2 == "e" ? w : 0) + y += (align == "t" ? -h : parent.Height) + } else { + y += offset + (align2 == "e" || align2 == "c" ? parent.Height : 0) - (align2 == "e" ? h : 0) + x += (align == "l" ? -w : parent.Width) + } + } + + screens.push({ + ID: `${screen.id}`, + Name: screen.name ?? `Display${screen.id}`, + ScaleFactor: Math.round(screen.s * 100) / 100, + X: x, + Y: y, + Size: {Width: w, Height: h}, + Bounds: {X: x, Y: y, Width: w, Height: h}, + PhysicalBounds: {X: x, Y: y, Width: w, Height: h}, + WorkArea: {X: x, Y: y, Width: w, Height: h-Math.round(40*screen.s)}, + PhysicalWorkArea: {X: x, Y: y, Width: w, Height: h-Math.round(40*screen.s)}, + IsPrimary: screen.id == 1, + Rotation: 0 + }) + } + + return {name: layout.name, screens} +} diff --git a/v3/examples/screen/assets/index.html b/v3/examples/screen/assets/index.html new file mode 100644 index 000000000..358624411 --- /dev/null +++ b/v3/examples/screen/assets/index.html @@ -0,0 +1,141 @@ + + + + + Screens Demo + + + +
+ + +
+ +  X: + + +  Width: + + +   + + +   + + +   + +   + Layers: +
+
+
+
+ Screens:  + System + +  - Examples + : +   + + +
+
+ Coordinates:  + Physical (PX) + Logical (DIP) + + +   + + + + + +
+
+ + + + + + +
+ + + + + + diff --git a/v3/examples/screen/assets/main.js b/v3/examples/screen/assets/main.js new file mode 100644 index 000000000..316c08002 --- /dev/null +++ b/v3/examples/screen/assets/main.js @@ -0,0 +1,406 @@ +setExamplesType(document.getElementById('examples-type').value, 0) + +function setExamplesType(type, autoSelectLayout = 1) { + window.examples_type = parseInt(type) + document.getElementById('examples-list').innerHTML = examples[examples_type].map((layout, i) => { + return `${i + 1}` + }).join("\n") + if (autoSelectLayout != null) setLayout(autoSelectLayout) +} + +async function setLayout(indexOrLayout, physicalCoordinate = true) { + if (typeof indexOrLayout == 'number') { + await radioBtnClick(null, `#layout-selector [data-value="${indexOrLayout}"]`) + } else { + document.querySelectorAll('#layout-selector .active').forEach(el => el.classList.remove('active')) + window.layout = indexOrLayout + window.point = null + window.rect = null + await processLayout() + await draw() + } + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + if (physical != physicalCoordinate) { + await setCoordinateType(physicalCoordinate) + } +} + +async function setCoordinateType(physicalCoordinate = true) { + await radioBtnClick(null, `#coordinate-selector [data-value="${physicalCoordinate ? 0 : 1}"]`) +} + +async function radioBtnClick(e, selector) { + if (e == null) { + e = new Event("mousedown") + document.querySelector(selector).dispatchEvent(e) + } + if (!e.target.classList.contains('radio-btn')) return + const btnGroup = e.target.closest('.radio-btn-group') + btnGroup.querySelectorAll('.radio-btn.active').forEach(el => el.classList.remove('active')) + e.target.classList.add('active') + + if (btnGroup.id == 'layout-selector') { + window.point = null + window.rect = null + await processLayout() + } + + await draw() +} + +async function processLayout() { + const layoutBtn = document.querySelector('#layout-selector .active') + const i = layoutBtn ? parseInt(layoutBtn.dataset.value) : -1 + if (i == 0) { + // system screens + window.layout = { + name: '', + screens: await callBinding('main.ScreenService.GetSystemScreens'), + } + } else { + if (i > 0) { + // example layouts + window.layout = structuredClone(examples[examples_type][i - 1]) + } + layout.screens = await callBinding('main.ScreenService.ProcessExampleScreens', layout.screens) + } + document.getElementById('example-name').textContent = layout.name +} + +async function draw() { + console.log(layout) + let minX = 0, minY = 0, maxX = 0, maxY = 0; + let html = ''; + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + const retainViewbox = document.querySelector('#retain-viewbox').checked + + layout.screens.forEach(screen => { + const b = physical ? screen.PhysicalBounds : screen.Bounds + const wa = physical ? screen.PhysicalWorkArea : screen.WorkArea + const vbBounds = retainViewbox ? [screen.Bounds, screen.PhysicalBounds] : [b] + + minX = Math.min(minX, ...vbBounds.map(b => b.X)) + minY = Math.min(minY, ...vbBounds.map(b => b.Y)) + maxX = Math.max(maxX, ...vbBounds.map(b => b.X + b.Width)) + maxY = Math.max(maxY, ...vbBounds.map(b => b.Y + b.Height)) + + html += ` + + + + + + (${b.X}, ${b.Y}) + + ${screen.Name} + ${b.Width} x ${b.Height} + Scale factor: ${screen.ScaleFactor} + + + ` + }) + + const svg = document.getElementById('svg') + svg.innerHTML = ` + ${svg.querySelector('& > defs').outerHTML} + + ${html} + + + ` + + svg.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`) + + if (window.point) await probePoint() + if (window.rect) await drawRect() + + svg.onmousedown = async function(e) { + let pt = new DOMPoint(e.clientX, e.clientY) + pt = pt.matrixTransform(svg.getScreenCTM().inverse()) + pt.x = parseInt(pt.x) + pt.y = parseInt(pt.y) + if (e.buttons == 1) { + await probePoint({X: pt.x, Y: pt.y}) + } else if (e.buttons == 2) { + if (e.ctrlKey) { + if (!window.rect) { + window.rect = {X: pt.x, Y: pt.y, Width: 0, Height: 0} + } + if (!window.rectCursor) { + window.rectAnchor = {x: window.rect.X, y: window.rect.Y} + window.rectCursor = {x: window.rectAnchor.x + window.rect.Width, y: window.rectAnchor.y + window.rect.Height} + } + window.rectCursorOffset = { + x: pt.x - window.rectCursor.x, + y: pt.y - window.rectCursor.y, + } + } else { + window.rectAnchor = pt + window.rectCursorOffset = {x: 0, y: 0} + window.probing = true + drawRect({X: pt.x, Y: pt.y, Width: 0, Height: 0}) + window.probing = false + } + } else if (e.buttons == 4) { + drawRect({X: pt.x, Y: pt.y, Width: 50, Height: 50}) + } + } + svg.onmousemove = async function(e) { + if (window.probing) return + window.probing = true + if (e.buttons == 1) { + await svg.onmousedown(e) + } else if (e.buttons == 2) { + let pt = new DOMPoint(e.clientX, e.clientY) + pt = pt.matrixTransform(svg.getScreenCTM().inverse()) + if (e.ctrlKey) { + window.rectAnchor.x += pt.x - rectCursor.x - window.rectCursorOffset.x + window.rectAnchor.y += pt.y - rectCursor.y - window.rectCursorOffset.y + } + window.rectCursor = { + x: pt.x - window.rectCursorOffset.x, + y: pt.y - window.rectCursorOffset.y, + } + await drawRect({ + X: parseInt(Math.min(window.rectAnchor.x, window.rectCursor.x)), + Y: parseInt(Math.min(window.rectAnchor.y, window.rectCursor.y)), + Width: parseInt(Math.abs(window.rectCursor.x - window.rectAnchor.x)), + Height: parseInt(Math.abs(window.rectCursor.y - window.rectAnchor.y)), + }) + } + window.probing = false + } + svg.oncontextmenu = function(e) { + e.preventDefault() + } +} + +async function probePoint(p = null) { + const svg = document.getElementById('svg'); + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + + if (p == null) { + if (window.pointIsPhysical == physical) { + p = window.point + } else { + p = (await callBinding('main.ScreenService.TransformPoint', window.point, window.pointIsPhysical))[0] + } + } + + window.point = p + window.pointIsPhysical = physical + const [ptTransformed, ptDblTransformed] = await callBinding('main.ScreenService.TransformPoint', p, physical) + + svg.getElementById('points').innerHTML = ` + + + + + ` + // await new Promise((resolve) => setTimeout(resolve, 200)) // delay + return ptDblTransformed +} + +async function drawRect(r = null) { + const svg = document.getElementById('svg'); + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + + if (r == null) { + if (window.rectIsPhysical == physical) { + r = window.rect + } else { + r = await callBinding('main.ScreenService.TransformRect', window.rect, window.rectIsPhysical) + } + } + + if (!window.probing) { + window.rectAnchor = null + window.rectCursor = null + } + + document.getElementById('x').value = r.X + document.getElementById('y').value = r.Y + document.getElementById('w').value = r.Width + document.getElementById('h').value = r.Height + + window.rect = r + window.rectIsPhysical = physical + window.rTransformed = await callBinding('main.ScreenService.TransformRect', r, physical) + window.rDblTransformed = await callBinding('main.ScreenService.TransformRect', rTransformed, !physical) + window.rTransformed = rTransformed + + await rectLayers() + return rDblTransformed +} + +async function rectLayers() { + const s = document.getElementById('slider').value + if (window.rect == null) await test1() + + const r = await callBinding('main.ScreenService.TransformRect', rectIsPhysical ? rect : rTransformed, true) + const rShifted = {...r, X: r.X+50} + const rShiftedPhysical = await callBinding('main.ScreenService.TransformRect', rShifted, false) + + svg.getElementById('rects').innerHTML = [ + [window.rect, 'rgb(255 255 255 / 100%)'], // w + [window.rTransformed, 'rgb(0 255 0 / 25%)'], // g + [window.rDblTransformed, 'none'], // none + [rShifted, 'rgb(255 0 0 / 15%)'], // r + [rShiftedPhysical, 'rgb(0 0 255 / 15%)'], // b + ].filter((_,i) => i { + let lines = '' + if (i == 0) { + const center = {X: r.X + (r.Width-1)/2, Y: r.Y + (r.Height-1)/2} + lines += ` + + + ` + } + return `${lines}` + }).join('/n') +} + +async function updateDipRect(x, y=0, w=0, h=0) { + if (rect == null) { + await drawRect({ + X: +document.getElementById('x').value, + Y: +document.getElementById('y').value, + Width: +document.getElementById('w').value, + Height: +document.getElementById('h').value, + }) + } + // Simulate real window by first retrieving the physical bounds then transforming it to dip + // then updating the bounds and transforming it back to physical + let rPhysical = rectIsPhysical ? rect : rTransformed + const r = await callBinding('main.ScreenService.TransformRect', rPhysical, true) + r.X += x + r.Y += y + r.Width += w + r.Height += h + rPhysical = await callBinding('main.ScreenService.TransformRect', r, false) + drawRect(rectIsPhysical ? rPhysical : r) +} + +function arrowMove(e) { + let x = 0, y = 0 + if (e.key == 'ArrowLeft') x = -step.value + if (e.key == 'ArrowRight') x = +step.value + if (e.key == 'ArrowUp') y = -step.value + if (e.key == 'ArrowDown') y = +step.value + if (!(x || y)) return + e.preventDefault() + updateDipRect(x, y) +} + +async function test1() { + // Edge case 1: invalid dip rect: no physical rect can produce it + await setLayout(parseLayout({screens: [ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1100, s: 1.5, parent: {id: 1, align: "r", offset: 0}}, + ]}), false) + await drawRect({X: 1050, Y: 700, Width: 400, Height: 300}) +} + +async function test2() { + // Edge case 2: physical rect that changes when double transformed (2 physical rects produce the same dip rect) + await setLayout(parseLayout({screens: [ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 900, s: 1, parent: {id: 1, align: "r", offset: 0}}, + ]}), true) + await drawRect({X: 1050, Y: 890, Width: 400, Height: 300}) +} + +async function probeLayout(finishup = true) { + const probeButtons = document.getElementById('probe-buttons') + const svg = document.getElementById('svg') + const threshold = 1 + + const physical = !parseInt(document.querySelector('#coordinate-selector .active').dataset.value) + window.cancelProbing = false + probeButtons.classList.add('active') + + const steps = 3 + let failed = false + for (const screen of layout.screens) { + if (window.cancelProbing) break + const b = physical ? screen.PhysicalBounds : screen.Bounds + const xStep = parseInt(b.Width / steps) || 1 + const yStep = parseInt(b.Height / steps) || 1 + let x = b.X, y = b.Y + let xDone = false, yDone = false + + while (!(yDone || window.cancelProbing)) { + if (y >= b.Y + b.Height - 1) { + y = b.Y + b.Height - 1 + yDone = true + } + x = b.X + xDone = false + while (!(xDone || window.cancelProbing)) { + if (x >= b.X + b.Width - 1) { + x = b.X + b.Width - 1 + xDone = true + } + const pt = {X: x, Y: y} + let ptDblTransformed, err + try { + ptDblTransformed = await probePoint(pt) + } catch (e) { + err = e + } + if (err || Math.abs(pt.X - ptDblTransformed.X) > threshold || Math.abs(pt.Y - ptDblTransformed.Y) > threshold) { + failed = true + console.log(pt, ptDblTransformed) + window.cancelProbing = true + setTimeout(() => { + alert(err ?? `**FAILED**\nProbing failed at point: {X: ${pt.X}, Y: ${pt.Y}}\nDouble transformed point: {X: ${ptDblTransformed.X}, Y: ${ptDblTransformed.Y}}\n(Exceeded threshold of ${threshold} pixels)`) + }, 50) + } + x += xStep + } + y += yStep + } + } + + if (finishup || window.cancelProbing) probeButtons.classList.remove('active') + if (!(failed || window.cancelProbing)) { + window.point = null + if (finishup) { + setTimeout(() => { + svg.getElementById('points').innerHTML = '' + alert(`Successfully probed all points!, All within threshold of ${threshold} pixels.`) + }, 50) + } + return true + } +} + +async function probeAllExamples() { + console.time('probeAllExamples') +loop1: + for (let typeI = 0; typeI < examples.length; typeI++) { + document.getElementById('examples-type').value = typeI + setExamplesType(typeI, null) + + for (let layoutI = (typeI ? 0 : -1); layoutI < examples[typeI].length; layoutI++) { + await radioBtnClick(null, `#layout-selector [data-value="${layoutI + 1}"]`) + for (let i = 0; i < 2; i++) { + const lastLayout = (typeI == examples.length - 1 && layoutI == examples[typeI].length - 1 && i == 1) + if (!await probeLayout(lastLayout)) break loop1 + if (i == 0) await setCoordinateType(!pointIsPhysical) + } + } + } + console.timeEnd('probeAllExamples') +} + +async function callBinding(name, ...params) { + return wails.Call.ByName(name, ...params) +} + +function showAdvanced(e) { + e.target.style.display = 'none' + document.querySelectorAll('.advanced').forEach(el => el.style.display = 'initial') +} diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go new file mode 100644 index 000000000..75d0c8bd2 --- /dev/null +++ b/v3/examples/screen/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "embed" + "log" + "log/slog" + "net/http" + "os" + "path/filepath" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Screen Demo", + Description: "A demo of the Screen API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + Services: []application.Service{ + application.NewService(&ScreenService{}), + }, + LogLevel: slog.LevelError, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + Middleware: func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Disable caching + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Dir(filename) + url := r.URL.Path + path := dir + "/assets" + url + + if _, err := os.Stat(path); err == nil { + // Serve file from disk to make testing easy + http.ServeFile(w, r, path) + } else { + // Passthrough to the default asset handler if file not found on disk + next.ServeHTTP(w, r) + } + }) + }, + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Screen Demo", + Width: 800, + Height: 600, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/screen/screens.go b/v3/examples/screen/screens.go new file mode 100644 index 000000000..a19afb14b --- /dev/null +++ b/v3/examples/screen/screens.go @@ -0,0 +1,137 @@ +package main + +import ( + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type ScreenService struct { + screenManager application.ScreenManager + isExampleLayout bool +} + +func (s *ScreenService) GetSystemScreens() []*application.Screen { + s.isExampleLayout = false + screens := application.Get().Screen.GetAll() + return screens +} + +func (s *ScreenService) ProcessExampleScreens(rawScreens []interface{}) []*application.Screen { + s.isExampleLayout = true + + parseRect := func(m map[string]interface{}) application.Rect { + return application.Rect{ + X: int(m["X"].(float64)), + Y: int(m["Y"].(float64)), + Width: int(m["Width"].(float64)), + Height: int(m["Height"].(float64)), + } + } + + // Prevent unbounded slice growth by limiting the number of screens + maxScreens := 32 // Reasonable limit for screen configurations + if len(rawScreens) > maxScreens { + rawScreens = rawScreens[:maxScreens] + } + + screens := make([]*application.Screen, 0, len(rawScreens)) + for _, s := range rawScreens { + s := s.(map[string]interface{}) + + bounds := parseRect(s["Bounds"].(map[string]interface{})) + + screens = append(screens, &application.Screen{ + ID: s["ID"].(string), + Name: s["Name"].(string), + X: bounds.X, + Y: bounds.Y, + Size: application.Size{Width: bounds.Width, Height: bounds.Height}, + Bounds: bounds, + PhysicalBounds: parseRect(s["PhysicalBounds"].(map[string]interface{})), + WorkArea: parseRect(s["WorkArea"].(map[string]interface{})), + PhysicalWorkArea: parseRect(s["PhysicalWorkArea"].(map[string]interface{})), + IsPrimary: s["IsPrimary"].(bool), + ScaleFactor: float32(s["ScaleFactor"].(float64)), + Rotation: 0, + }) + } + + s.screenManager.LayoutScreens(screens) + return s.screenManager.GetAll() +} + +func (s *ScreenService) transformPoint(point application.Point, toDIP bool) application.Point { + if s.isExampleLayout { + if toDIP { + return s.screenManager.PhysicalToDipPoint(point) + } else { + return s.screenManager.DipToPhysicalPoint(point) + } + } else { + // ======================= + // TODO: remove this block when DPI is implemented in Linux & Mac + if runtime.GOOS != "windows" { + println("DPI not implemented yet!") + return point + } + // ======================= + if toDIP { + return application.PhysicalToDipPoint(point) + } else { + return application.DipToPhysicalPoint(point) + } + } +} + +func (s *ScreenService) TransformPoint(point map[string]interface{}, toDIP bool) (points [2]application.Point) { + pt := application.Point{ + X: int(point["X"].(float64)), + Y: int(point["Y"].(float64)), + } + + ptTransformed := s.transformPoint(pt, toDIP) + ptDblTransformed := s.transformPoint(ptTransformed, !toDIP) + + // double-transform a limited number of times to catch any double-rounding issues + // Limit iterations to prevent potential performance issues + maxIterations := 3 // Reduced from 10 to limit computational overhead + for i := 0; i < maxIterations; i++ { + ptTransformed = s.transformPoint(ptDblTransformed, toDIP) + ptDblTransformed = s.transformPoint(ptTransformed, !toDIP) + } + + points[0] = ptTransformed + points[1] = ptDblTransformed + return points +} + +func (s *ScreenService) TransformRect(rect map[string]interface{}, toDIP bool) application.Rect { + r := application.Rect{ + X: int(rect["X"].(float64)), + Y: int(rect["Y"].(float64)), + Width: int(rect["Width"].(float64)), + Height: int(rect["Height"].(float64)), + } + + if s.isExampleLayout { + if toDIP { + return s.screenManager.PhysicalToDipRect(r) + } else { + return s.screenManager.DipToPhysicalRect(r) + } + } else { + // ======================= + // TODO: remove this block when DPI is implemented in Linux & Mac + if runtime.GOOS != "windows" { + println("DPI not implemented yet!") + return r + } + // ======================= + if toDIP { + return application.PhysicalToDipRect(r) + } else { + return application.DipToPhysicalRect(r) + } + } +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js new file mode 100644 index 000000000..cb6c1ff84 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Hashes} Hashes + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js new file mode 100644 index 000000000..a48737a6b --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * @typedef {Object} Hashes + * @property {string} md5 + * @property {string} sha1 + * @property {string} sha256 + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js new file mode 100644 index 000000000..f5c01b306 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/examples/services/hashes/service.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {string} s + * @returns {$CancellablePromise<$models.Hashes>} + */ +export function Generate(s) { + return $Call.ByID(1123907498, s); +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js new file mode 100644 index 000000000..d2bf43c94 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/keyvaluestore.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/keyvaluestore.js new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js new file mode 100644 index 000000000..b4285097c --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/service.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +/** + * Clear deletes all keys from the store. If AutoSave is true, the store is saved to disk. + * @returns {Promise & { cancel(): void }} + */ +export function Clear() { + let $resultPromise = /** @type {any} */($Call.ByID(816318109)); + return $resultPromise; +} + +/** + * Delete deletes the given key from the store. If AutoSave is true, the store is saved to disk. + * @param {string} key + * @returns {Promise & { cancel(): void }} + */ +export function Delete(key) { + let $resultPromise = /** @type {any} */($Call.ByID(2889946731, key)); + return $resultPromise; +} + +/** + * Get returns the value for the given key. If key is empty, the entire store is returned. + * @param {string} key + * @returns {Promise & { cancel(): void }} + */ +export function Get(key) { + let $resultPromise = /** @type {any} */($Call.ByID(376909388, key)); + return $resultPromise; +} + +/** + * Load loads the store from disk. + * If the store is in-memory, i.e. not associated with a file, Load has no effect. + * If the operation fails, a non-nil error is returned + * and the store's content and state at call time are preserved. + * @returns {Promise & { cancel(): void }} + */ +export function Load() { + let $resultPromise = /** @type {any} */($Call.ByID(1850778156)); + return $resultPromise; +} + +/** + * Save saves the store to disk. + * If the store is in-memory, i.e. not associated with a file, Save has no effect. + * @returns {Promise & { cancel(): void }} + */ +export function Save() { + let $resultPromise = /** @type {any} */($Call.ByID(3572737965)); + return $resultPromise; +} + +/** + * Set sets the value for the given key. If AutoSave is true, the store is saved to disk. + * @param {string} key + * @param {any} value + * @returns {Promise & { cancel(): void }} + */ +export function Set(key, value) { + let $resultPromise = /** @type {any} */($Call.ByID(2491766752, key, value)); + return $resultPromise; +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js new file mode 100644 index 000000000..564a31eeb --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Level +} from "./models.js"; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js new file mode 100644 index 000000000..d8579b51a --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/models.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * A Level is the importance or severity of a log event. + * The higher the level, the more important or severe the event. + * + * Values are arbitrary, but there are four predefined ones. + * @typedef {number} Level + */ + +/** + * Predefined constants for type Level. + * @namespace + */ +export const Level = { + Debug: -4, + Info: 0, + Warning: 4, + Error: 8, +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js new file mode 100644 index 000000000..e5128d9bc --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/log/service.js @@ -0,0 +1,106 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * DebugContext logs at level [Debug]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function DebugContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(1481024990, message, args)); + return $resultPromise; +} + +/** + * ErrorContext logs at level [Error]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ErrorContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4028761057, message, args)); + return $resultPromise; +} + +/** + * InfoContext logs at level [Info]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function InfoContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(1400061359, message, args)); + return $resultPromise; +} + +/** + * Log emits a log record with the current time and the given level and message. + * The Record's attributes consist of the Logger's attributes followed by + * the attributes specified by args. + * + * The attribute arguments are processed as follows: + * - If an argument is a string and this is not the last argument, + * the following argument is treated as the value and the two are combined + * into an attribute. + * - Otherwise, the argument is treated as a value with key "!BADKEY". + * + * Log feeds the binding call context into the configured logger, + * so custom handlers may access context values, e.g. the current window. + * @param {$models.Level} level + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +export function Log(level, message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4156699346, level, message, args)); + return $resultPromise; +} + +/** + * LogLevel returns the currently configured log level, + * that is either the one configured initially + * or the last value passed to [Service.SetLogLevel]. + * @returns {Promise<$models.Level> & { cancel(): void }} + */ +export function LogLevel() { + let $resultPromise = /** @type {any} */($Call.ByID(4058368160)); + return $resultPromise; +} + +/** + * SetLogLevel changes the current log level. + * @param {$models.Level} level + * @returns {Promise & { cancel(): void }} + */ +export function SetLogLevel(level) { + let $resultPromise = /** @type {any} */($Call.ByID(3988219088, level)); + return $resultPromise; +} + +/** + * WarningContext logs at level [Warn]. + * @param {string} message + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function WarningContext(message, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(52282975, message, args)); + return $resultPromise; +} + +export { + DebugContext as Debug, + InfoContext as Info, + WarningContext as Warning, + ErrorContext as Error, +}; diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js new file mode 100644 index 000000000..0918a51f0 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * Row holds a single row in the result of a query. + * It is a key-value map where keys are column names. + * @typedef {$models.Row} Row + */ + +/** + * Rows holds the result of a query + * as an array of key-value maps where keys are column names. + * @typedef {$models.Rows} Rows + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js new file mode 100644 index 000000000..041151c86 --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/models.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "/wails/runtime.js"; + +/** + * Row holds a single row in the result of a query. + * It is a key-value map where keys are column names. + * @typedef {{ [_: string]: any }} Row + */ + +/** + * Rows holds the result of a query + * as an array of key-value maps where keys are column names. + * @typedef {Row[]} Rows + */ + +/** + * Stmt wraps a prepared sql statement pointer. + * It provides the same methods as the [sql.Stmt] type. + * @typedef {string} Stmt + */ diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js new file mode 100644 index 000000000..9330c1e4d --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js @@ -0,0 +1,167 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Close closes the current database connection if one is open, otherwise has no effect. + * Additionally, Close closes all open prepared statements associated to the connection. + * + * Even when a non-nil error is returned, + * the database service is left in a consistent state, + * ready for a call to [Service.Open]. + * @returns {Promise & { cancel(): void }} + */ +export function Close() { + let $resultPromise = /** @type {any} */($Call.ByID(1888105376)); + return $resultPromise; +} + +/** + * ClosePrepared closes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext]. + * ClosePrepared is idempotent: + * it has no effect on prepared statements that are already closed. + * @param {$models.Stmt | null} stmt + * @returns {Promise & { cancel(): void }} + */ +function ClosePrepared(stmt) { + let $resultPromise = /** @type {any} */($Call.ByID(2526200629, stmt)); + return $resultPromise; +} + +/** + * ExecContext executes a query without returning any rows. + * It supports early cancellation. + * @param {string} query + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ExecContext(query, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(674944556, query, args)); + return $resultPromise; +} + +/** + * ExecPrepared executes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext] + * without returning any rows. + * It supports early cancellation. + * @param {$models.Stmt | null} stmt + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ +function ExecPrepared(stmt, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(2086877656, stmt, args)); + return $resultPromise; +} + +/** + * Open validates the current configuration, + * closes the current connection if one is present, + * then opens and validates a new connection. + * + * Even when a non-nil error is returned, + * the database service is left in a consistent state, + * ready for a new call to Open. + * @returns {Promise & { cancel(): void }} + */ +export function Open() { + let $resultPromise = /** @type {any} */($Call.ByID(2012175612)); + return $resultPromise; +} + +/** + * PrepareContext creates a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently from the returned statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + * + * PrepareContext supports early cancellation. + * @param {string} query + * @returns {Promise<$models.Stmt | null> & { cancel(): void }} + */ +function PrepareContext(query) { + let $resultPromise = /** @type {any} */($Call.ByID(570941694, query)); + return $resultPromise; +} + +/** + * QueryContext executes a query and returns a slice of key-value records, + * one per row, with column names as keys. + * It supports early cancellation, returning the slice of results fetched so far. + * @param {string} query + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ +function QueryContext(query, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(4115542347, query, args)); + let $typingPromise = /** @type {any} */($resultPromise.then(($result) => { + return $$createType1($result); + })); + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * QueryPrepared executes a prepared statement + * obtained with [Service.Prepare] or [Service.PrepareContext] + * and returns a slice of key-value records, one per row, with column names as keys. + * It supports early cancellation, returning the slice of results fetched so far. + * @param {$models.Stmt | null} stmt + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ +function QueryPrepared(stmt, ...args) { + let $resultPromise = /** @type {any} */($Call.ByID(3885083725, stmt, args)); + let $typingPromise = /** @type {any} */($resultPromise.then(($result) => { + return $$createType1($result); + })); + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Array($$createType0); + +export { + ExecContext as Execute, + QueryContext as Query +}; + +import { Stmt } from "./stmt.js"; + +/** + * Prepare creates a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently from the returned statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + * + * Prepare supports early cancellation. + * + * @param {string} query + * @returns {Promise & { cancel(): void }} + */ +export function Prepare(query) { + const promise = PrepareContext(query); + const wrapper = /** @type {any} */(promise.then(function (id) { + return id == null ? null : new Stmt( + ClosePrepared.bind(null, id), + ExecPrepared.bind(null, id), + QueryPrepared.bind(null, id)); + })); + wrapper.cancel = promise.cancel; + return wrapper; +} diff --git a/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js new file mode 100644 index 000000000..948b0c3dd --- /dev/null +++ b/v3/examples/services/assets/bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/stmt.js @@ -0,0 +1,79 @@ +//@ts-check + +//@ts-ignore: Unused imports +import * as $models from "./models.js"; + +const execSymbol = Symbol("exec"), + querySymbol = Symbol("query"), + closeSymbol = Symbol("close"); + +/** + * Stmt represents a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently on the same statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + */ +export class Stmt { + /** + * Constructs a new prepared statement instance. + * @param {(...args: any[]) => Promise} close + * @param {(...args: any[]) => Promise & { cancel(): void }} exec + * @param {(...args: any[]) => Promise<$models.Rows> & { cancel(): void }} query + */ + constructor(close, exec, query) { + /** + * @member + * @private + * @type {typeof close} + */ + this[closeSymbol] = close; + + /** + * @member + * @private + * @type {typeof exec} + */ + this[execSymbol] = exec; + + /** + * @member + * @private + * @type {typeof query} + */ + this[querySymbol] = query; + } + + /** + * Closes the prepared statement. + * It has no effect when the statement is already closed. + * @returns {Promise} + */ + Close() { + return this[closeSymbol](); + } + + /** + * Executes the prepared statement without returning any rows. + * It supports early cancellation. + * + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ + Exec(...args) { + return this[execSymbol](...args); + } + + /** + * Executes the prepared statement + * and returns a slice of key-value records, one per row, with column names as keys. + * It supports early cancellation, returning the array of results fetched so far. + * + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ + Query(...args) { + return this[querySymbol](...args); + } +} diff --git a/v3/examples/services/assets/index.html b/v3/examples/services/assets/index.html new file mode 100644 index 000000000..c44a0679a --- /dev/null +++ b/v3/examples/services/assets/index.html @@ -0,0 +1,287 @@ + + + + + Wails Alpha + + + + + + +

Services

+
+
+ + + + +
+
+

The sqlite service provides easy integration with sqlite dbs.

+

The demo DB has a single table: Users.

+

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

+
+
+
+ +
+
+
+
+
+
+

The hashes service provides hashing functions.

+
+
+
+ +
+
+
+
+
+
+

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

+

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

+

A blank value will remove the key.

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

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

+

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

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

Switch this window to fullscreen: the toolbar will be hidden

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

Switch this window to fullscreen: the toolbar will stay visible

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

Single Instance Demo

+ +
+

Current Instance Information

+
Loading...
+
+ +
+

Instructions

+

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

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

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

+
+
+ + + + \ No newline at end of file diff --git a/v3/examples/single-instance/main.go b/v3/examples/single-instance/main.go new file mode 100644 index 000000000..51ca48a38 --- /dev/null +++ b/v3/examples/single-instance/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "embed" + "log" + "log/slog" + "os" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/index.html +var assets embed.FS + +type App struct{} + +func (a *App) GetCurrentInstanceInfo() map[string]interface{} { + return map[string]interface{}{ + "args": os.Args, + "workingDir": getCurrentWorkingDir(), + } +} + +var encryptionKey = [32]byte{ + 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, + 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, + 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, + 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, +} + +func main() { + + var window *application.WebviewWindow + app := application.New(application.Options{ + Name: "Single Instance Example", + LogLevel: slog.LevelDebug, + Description: "An example of single instance functionality in Wails v3", + Services: []application.Service{ + application.NewService(&App{}), + }, + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.wails.example.single-instance", + EncryptionKey: encryptionKey, + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + if window != nil { + window.EmitEvent("secondInstanceLaunched", data) + window.Restore() + window.Focus() + } + log.Printf("Second instance launched with args: %v\n", data.Args) + log.Printf("Working directory: %s\n", data.WorkingDir) + if data.AdditionalData != nil { + log.Printf("Additional data: %v\n", data.AdditionalData) + } + }, + AdditionalData: map[string]string{ + "launchtime": time.Now().Local().String(), + }, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Single Instance Demo", + Width: 800, + Height: 700, + URL: "/", + }) + + app.Run() +} + +func getCurrentWorkingDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} diff --git a/v3/examples/systray-basic/README.md b/v3/examples/systray-basic/README.md new file mode 100644 index 000000000..11b84a0bf --- /dev/null +++ b/v3/examples/systray-basic/README.md @@ -0,0 +1,16 @@ +# Systray Basic Example + +This example creates a simple system tray with an attached window. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/systray-basic/main.go b/v3/examples/systray-basic/main.go new file mode 100644 index 000000000..1f0450863 --- /dev/null +++ b/v3/examples/systray-basic/main.go @@ -0,0 +1,61 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + KeyBindings: map[string]func(window *application.WebviewWindow){ + "F12": func(window *application.WebviewWindow) { + systemTray.OpenMenu() + }, + }, + }) + + // Register a hook to hide the window when the window is closing + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Hide the window + window.Hide() + // Cancel the event so it doesn't get destroyed + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + systemTray.AttachWindow(window).WindowOffset(5) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/systray-custom/README.md b/v3/examples/systray-custom/README.md new file mode 100644 index 000000000..598d84496 --- /dev/null +++ b/v3/examples/systray-custom/README.md @@ -0,0 +1,16 @@ +# Systray Custom Example + +This example creates a simple system tray and uses hooks to attach a custom window. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | | +| Linux | | diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go new file mode 100644 index 000000000..f10140792 --- /dev/null +++ b/v3/examples/systray-custom/main.go @@ -0,0 +1,73 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" +) + +var windowShowing bool + +func createWindow(app *application.App) { + if windowShowing { + return + } + // Log the time taken to create the window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + AlwaysOnTop: true, + Hidden: true, + BackgroundColour: application.NewRGB(33, 37, 41), + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + }) + windowShowing = true + + window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + windowShowing = false + }) + + window.Show() +} + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Windows: application.WindowsOptions{ + DisableQuitOnLastWindowClosed: true, + }, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + menu := app.NewMenu() + menu.Add("Quit").OnClick(func(data *application.Context) { + app.Quit() + }) + systemTray.SetMenu(menu) + systemTray.SetTooltip("Systray Demo") + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + systemTray.OnClick(func() { + createWindow(app) + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/systray-menu/README.md b/v3/examples/systray-menu/README.md new file mode 100644 index 000000000..2de57faea --- /dev/null +++ b/v3/examples/systray-menu/README.md @@ -0,0 +1,17 @@ +# Systray Menu Example + +This example creates a system tray with an attached window and a menu. +The window is hidden by default and toggled by left-clicking on the systray icon. +The window will hide automatically when it loses focus. +Right-clicking on the systray icon will show the menu. + +On Windows, if the icon is in the notification flyout, +then the window will be shown in the bottom right corner. + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | Working | +| Linux | | diff --git a/v3/examples/systray-menu/logo-dark-xsmall.png b/v3/examples/systray-menu/logo-dark-xsmall.png new file mode 100644 index 000000000..83a514c74 Binary files /dev/null and b/v3/examples/systray-menu/logo-dark-xsmall.png differ diff --git a/v3/examples/systray-menu/main.go b/v3/examples/systray-menu/main.go new file mode 100644 index 000000000..38489bd52 --- /dev/null +++ b/v3/examples/systray-menu/main.go @@ -0,0 +1,114 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + "runtime" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +//go:embed logo-dark-xsmall.png +var logo []byte + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.SystemTray.New() + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Width: 500, + Height: 500, + Name: "Systray Demo Window", + Frameless: true, + AlwaysOnTop: true, + Hidden: true, + DisableResize: true, + Windows: application.WindowsWindow{ + HiddenOnTaskbar: true, + }, + KeyBindings: map[string]func(window *application.WebviewWindow){ + "F12": func(window *application.WebviewWindow) { + systemTray.OpenMenu() + }, + }, + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + window.Hide() + e.Cancel() + }) + + if runtime.GOOS == "darwin" { + systemTray.SetTemplateIcon(icons.SystrayMacTemplate) + } + + myMenu := app.NewMenu() + myMenu.Add("Wails").SetBitmap(logo).SetEnabled(false) + myMenu.Add("Hidden").SetHidden(true) + + myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) { + println("Hello World!") + q := application.QuestionDialog().SetTitle("Ready?").SetMessage("Are you feeling ready?") + q.AddButton("Yes").OnClick(func() { + println("Awesome!") + }) + q.AddButton("No").SetAsDefault().OnClick(func() { + println("Boo!") + }) + q.Show() + }) + subMenu := myMenu.AddSubmenu("Submenu") + subMenu.Add("Click me!").OnClick(func(ctx *application.Context) { + ctx.ClickedMenuItem().SetLabel("Clicked!") + }) + myMenu.AddSeparator() + myMenu.AddCheckbox("Checked", true).OnClick(func(ctx *application.Context) { + println("Checked: ", ctx.ClickedMenuItem().Checked()) + application.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show() + }) + myMenu.Add("Enabled").OnClick(func(ctx *application.Context) { + println("Click me!") + ctx.ClickedMenuItem().SetLabel("Disabled!").SetEnabled(false) + }) + myMenu.AddSeparator() + // Callbacks can be shared. This is useful for radio groups + radioCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel(menuItem.Label() + "!") + } + + // Radio groups are created implicitly by placing radio items next to each other in a menu + myMenu.AddRadio("Radio 1", true).OnClick(radioCallback) + myMenu.AddRadio("Radio 2", false).OnClick(radioCallback) + myMenu.AddRadio("Radio 3", false).OnClick(radioCallback) + myMenu.AddSeparator() + myMenu.Add("Hide System tray for 3 seconds...").OnClick(func(ctx *application.Context) { + systemTray.Hide() + time.Sleep(3 * time.Second) + systemTray.Show() + }) + myMenu.AddSeparator() + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + + systemTray.AttachWindow(window).WindowOffset(2) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/video/README.md b/v3/examples/video/README.md new file mode 100644 index 000000000..012469afb --- /dev/null +++ b/v3/examples/video/README.md @@ -0,0 +1,19 @@ +# Video Example + +This example shows support for HTML5 video. + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | | +| Windows | Working | +| Linux | | diff --git a/v3/examples/video/main.go b/v3/examples/video/main.go new file mode 100644 index 000000000..879172c61 --- /dev/null +++ b/v3/examples/video/main.go @@ -0,0 +1,47 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/events" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Video Demo", + Description: "A demo of HTML5 Video API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Windows: application.WindowsOptions{ + WndProcInterceptor: nil, + DisableQuitOnLastWindowClosed: false, + WebviewUserDataPath: "", + WebviewBrowserPath: "", + }, + }) + app.Event.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(event *application.ApplicationEvent) { + log.Println("ApplicationDidFinishLaunching") + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + WebviewPreferences: application.MacWebviewPreferences{ + FullscreenEnabled: application.Enabled, + }, + }, + HTML: "", + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/window-api/README.md b/v3/examples/window-api/README.md new file mode 100644 index 000000000..02c726062 --- /dev/null +++ b/v3/examples/window-api/README.md @@ -0,0 +1,19 @@ +# Window API Example + +This is an example of how to use the JS Window API + +## Running the example + +To run the example, simply run the following command: + +```bash +go run . +``` + +# Status + +| Platform | Status | +|----------|---------| +| Mac | Working | +| Windows | | +| Linux | | diff --git a/v3/examples/window-api/assets/index.html b/v3/examples/window-api/assets/index.html new file mode 100644 index 000000000..a0dd5d8b2 --- /dev/null +++ b/v3/examples/window-api/assets/index.html @@ -0,0 +1,159 @@ + + + + + Wails Alpha + + + + + +
Alpha
+
+

Close the Window?

+

Center

+

Minimise

+

Maximise

+

UnMaximise

+

Fullscreen

+

UnFullscreen

+

Restore

+
+
+

ToggleMaximise

+

IsFocused

+

IsMaximised

+

IsFullscreen

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

About Window Menu Demo

+

Press F1 to toggle menu bar visibility

+

Press F2 to show menu bar

+

Press F3 to hide menu bar

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

Window Menu Demo

+

This example demonstrates the menu bar visibility toggle feature.

+

Press F1 to toggle the menu bar.

+

Press F2 to show the menu bar.

+

Press F3 to hide the menu bar.

+

The menu includes:

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

A MacTitleBarHiddenInset WebviewWindow example

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

A MacTitleBarHiddenInsetUnified WebviewWindow example

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

A MacTitleBarHidden WebviewWindow example

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

This is a Window with a Mica backdrop

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

This is a Window with an Acrylic backdrop

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

This is a Window with a Tabbed-effect backdrop

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

Documentation

+

Feedback

+
+

This application contains no Javascript!

+

Emit event

+

Delete all the things!

+

Close the Window?

+

Center

+

Minimise

+

Maximise

+

UnMaximise

+

Fullscreen

+

UnFullscreen

+

Restore

+

Open Browser?

+

Hover over me

+
+ + + + + diff --git a/v3/examples/wml/main.go b/v3/examples/wml/main.go new file mode 100644 index 000000000..8d4a55481 --- /dev/null +++ b/v3/examples/wml/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "embed" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Wails ML Demo", + Description: "A demo of the Wails ML API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails ML Demo", + Width: 1280, + Height: 1024, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.Event.On("button-pressed", func(_ *application.CustomEvent) { + println("Button Pressed!") + }) + app.Event.On("hover", func(_ *application.CustomEvent) { + println("Hover time!") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/go.mod b/v3/go.mod new file mode 100644 index 000000000..3684bbff2 --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,151 @@ +module github.com/wailsapp/wails/v3 + +go 1.24.0 + +require ( + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 + github.com/Masterminds/semver v1.5.0 + github.com/adrg/xdg v0.5.3 + github.com/atterpac/refresh v0.8.6 + github.com/bep/debounce v1.2.1 + github.com/charmbracelet/glamour v0.9.0 + github.com/ebitengine/purego v0.8.2 + github.com/go-git/go-git/v5 v5.13.2 + github.com/go-ole/go-ole v1.3.0 + github.com/godbus/dbus/v5 v5.1.0 + github.com/google/go-cmp v0.7.0 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/uuid v1.6.0 + github.com/goreleaser/nfpm/v2 v2.41.3 + github.com/jackmordaunt/icns/v2 v2.2.7 + github.com/jaypipes/ghw v0.17.0 + github.com/leaanthony/clir v1.7.0 + github.com/leaanthony/go-ansi-parser v1.6.1 + github.com/leaanthony/gosod v1.0.4 + github.com/leaanthony/u v1.1.1 + github.com/leaanthony/winicon v1.0.0 + github.com/lmittmann/tint v1.0.7 + github.com/matryer/is v1.4.1 + github.com/mattn/go-colorable v0.1.14 + github.com/mattn/go-isatty v0.0.20 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/pkg/errors v0.9.1 + github.com/pterm/pterm v0.12.80 + github.com/samber/lo v1.49.1 + github.com/tc-hib/winres v0.3.1 + github.com/wailsapp/go-webview2 v1.0.21 + github.com/wailsapp/mimetype v1.4.1 + github.com/wailsapp/task/v3 v3.40.1-patched3 + golang.org/x/sys v0.31.0 + golang.org/x/term v0.30.0 + golang.org/x/tools v0.31.0 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.36.0 +) + +require ( + atomicgo.dev/schedule v0.1.0 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/AlekSi/pointer v1.2.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/Ladicle/tabwriter v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/alecthomas/chroma/v2 v2.15.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect + github.com/cavaliergopher/cpio v1.0.1 // indirect + github.com/chainguard-dev/git-urls v1.0.2 // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/containerd/console v1.0.4 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dominikbraun/graph v0.23.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-task/template v0.1.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/goreleaser/chglog v0.6.2 // indirect + github.com/goreleaser/fileglob v1.3.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/jaypipes/pcidb v1.0.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-zglob v0.0.6 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/radovskyb/watcher v1.0.7 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rjeczalik/notify v0.9.3 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.7.8 // indirect + github.com/yuin/goldmark-emoji v1.0.5 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac + golang.org/x/image v0.24.0 + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + howett.net/plist v1.0.1 // indirect + modernc.org/libc v1.61.13 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.2 // indirect + mvdan.cc/sh/v3 v3.10.0 // indirect +) diff --git a/v3/go.sum b/v3/go.sum new file mode 100644 index 000000000..1f4983e7a --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,466 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= +github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= +github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= +github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= +github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= +github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= +github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/atterpac/refresh v0.8.6 h1:Q5miKV2qs9jW+USw8WZ/54Zz8/RSh/bOz5U6JvvDZmM= +github.com/atterpac/refresh v0.8.6/go.mod h1:fJpWySLdpbANS8Ej5OvfZVZIVvi/9bmnhTjKS5EjQes= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= +github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= +github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= +github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= +github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= +github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/glamour v0.9.0 h1:1Hm3wxww7qXvGI+Fb3zDmIZo5oDOvVOWJ4OrIB+ef7c= +github.com/charmbracelet/glamour v0.9.0/go.mod h1:+SHvIS8qnwhgTpVMiXwn7OfGomSqff1cHBCI8jLOetk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= +github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= +github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE= +github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= +github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/goreleaser/chglog v0.6.2 h1:qroqdMHzwoAPTHHzJtbCfYbwg/yWJrNQApZ6IQAq8bU= +github.com/goreleaser/chglog v0.6.2/go.mod h1:BP0xQQc6B8aM+4dhvSLlVTv0rvhuOF0JacDO1+h7L3U= +github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I= +github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU= +github.com/goreleaser/nfpm/v2 v2.41.3 h1:IRRsqv5NgiCKUy57HjQgfVBFb44VH8+r1mWeEF8OuA4= +github.com/goreleaser/nfpm/v2 v2.41.3/go.mod h1:0t54RfPX6/iKANsVLbB3XgtfQXzG1nS4HmSavN92qVY= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jackmordaunt/icns/v2 v2.2.7 h1:K/RbfvuzjmjVY5y4g+XENRs8ZZatwz4YnLHypa2KwQg= +github.com/jackmordaunt/icns/v2 v2.2.7/go.mod h1:ovoTxGguSuoUGKMk5Nn3R7L7BgMQkylsO+bblBuI22A= +github.com/jaypipes/ghw v0.17.0 h1:EVLJeNcy5z6GK/Lqby0EhBpynZo+ayl8iJWY0kbEUJA= +github.com/jaypipes/ghw v0.17.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8= +github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic= +github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/clir v1.7.0 h1:xiAnhl7ryPwuH3ERwPWZp/pCHk8wTeiwuAOt6MiNyAw= +github.com/leaanthony/clir v1.7.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= +github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A= +github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= +github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= +github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4= +github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/task/v3 v3.40.1-patched3 h1:i6O1WNdSur9CGaiMDIYGjsmj/qS4465zqv+WEs6sPRs= +github.com/wailsapp/task/v3 v3.40.1-patched3/go.mod h1:jIP48r8ftoSQNlxFP4+aEnkvGQqQXqCnRi/B7ROaecE= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= +github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= +gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= +modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= +modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= +modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= +modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8= +modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= +mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= diff --git a/v3/internal/assetserver/asset_fileserver.go b/v3/internal/assetserver/asset_fileserver.go new file mode 100644 index 000000000..974bb582b --- /dev/null +++ b/v3/internal/assetserver/asset_fileserver.go @@ -0,0 +1,161 @@ +package assetserver + +import ( + "bytes" + "context" + "embed" + "errors" + "fmt" + "io" + iofs "io/fs" + "net/http" + "os" + "path" + "strings" +) + +const ( + indexHTML = "index.html" +) + +type assetFileServer struct { + fs iofs.FS + err error +} + +func newAssetFileServerFS(vfs iofs.FS) http.Handler { + subDir, err := findPathToFile(vfs, indexHTML) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + msg := "no `index.html` could be found in your Assets fs.FS" + if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs { + rootFolder, _ := findEmbedRootPath(embedFs) + msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder) + } + + err = errors.New(msg) + } + } else { + vfs, err = iofs.Sub(vfs, path.Clean(subDir)) + } + + return &assetFileServer{fs: vfs, err: err} +} + +func (d *assetFileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + url := req.URL.Path + + err := d.err + if err == nil { + filename := path.Clean(strings.TrimPrefix(url, "/")) + d.logInfo(ctx, "Handling request", "url", url, "file", filename) + err = d.serveFSFile(rw, req, filename) + if os.IsNotExist(err) { + rw.WriteHeader(http.StatusNotFound) + return + } + } + + if err != nil { + d.logError(ctx, "Unable to handle request", "url", url, "err", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + } +} + +// serveFile will try to load the file from the fs.FS and write it to the response +func (d *assetFileServer) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { + if d.fs == nil { + return os.ErrNotExist + } + + file, err := d.fs.Open(filename) + if err != nil { + if s := path.Ext(filename); s == "" { + filename = filename + ".html" + file, err = d.fs.Open(filename) + if err != nil { + return err + } + } else { + return err + } + } + defer file.Close() + + statInfo, err := file.Stat() + if err != nil { + return err + } + + url := req.URL.Path + isDirectoryPath := url == "" || url[len(url)-1] == '/' + if statInfo.IsDir() { + if !isDirectoryPath { + // If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on + // WebKit WebViews (macOS/Linux). + // So we handle this as a specific error + return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request") + } + + filename = path.Join(filename, indexHTML) + + file, err = d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err = file.Stat() + if err != nil { + return err + } + } else if isDirectoryPath { + return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request") + } + + var buf [512]byte + var n int + if _, haveType := rw.Header()[HeaderContentType]; !haveType { + // Detect MimeType by sniffing the first 512 bytes + n, err = file.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + + // Do the custom MimeType sniffing even though http.ServeContent would do it in case + // of an io.ReadSeeker. We would like to have a consistent behaviour in both cases. + if contentType := GetMimetype(filename, buf[:n]); contentType != "" { + rw.Header().Set(HeaderContentType, contentType) + } + } + + if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil { + if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("seeker can't seek") + } + + http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker) + return nil + } + + rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size())) + + // Write the first 512 bytes used for MimeType sniffing + _, err = io.Copy(rw, bytes.NewReader(buf[:n])) + if err != nil { + return err + } + + // Copy the remaining content of the file + _, err = io.Copy(rw, file) + return err +} + +func (d *assetFileServer) logInfo(ctx context.Context, message string, args ...interface{}) { + logInfo(ctx, "[AssetFileServerFS] "+message, args...) +} + +func (d *assetFileServer) logError(ctx context.Context, message string, args ...interface{}) { + logError(ctx, "[AssetFileServerFS] "+message, args...) +} diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go new file mode 100644 index 000000000..6ff1b169f --- /dev/null +++ b/v3/internal/assetserver/assetserver.go @@ -0,0 +1,175 @@ +package assetserver + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +const ( + webViewRequestHeaderWindowId = "x-wails-window-id" + webViewRequestHeaderWindowName = "x-wails-window-name" + HeaderAcceptLanguage = "accept-language" +) + +type RuntimeHandler interface { + HandleRuntimeCall(w http.ResponseWriter, r *http.Request) +} + +type service struct { + Route string + Handler http.Handler +} + +type AssetServer struct { + options *Options + handler http.Handler + services []service + + assetServerWebView +} + +func NewAssetServer(options *Options) (*AssetServer, error) { + result := &AssetServer{ + options: options, + } + + userHandler := options.Handler + if userHandler == nil { + userHandler = http.NotFoundHandler() + } + + handler := http.Handler( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + result.serveHTTP(w, r, userHandler) + })) + + if middleware := options.Middleware; middleware != nil { + handler = middleware(handler) + } + + result.handler = handler + + return result, nil +} + +func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + start := time.Now() + wrapped := newContentTypeSniffer(rw) + defer func() { + if _, err := wrapped.complete(); err != nil { + a.options.Logger.Error("Error writing response data.", "uri", req.RequestURI, "error", err) + } + }() + + req = req.WithContext(contextWithLogger(req.Context(), a.options.Logger)) + a.handler.ServeHTTP(wrapped, req) + + a.options.Logger.Info( + "Asset Request:", + "windowName", req.Header.Get(webViewRequestHeaderWindowName), + "windowID", req.Header.Get(webViewRequestHeaderWindowId), + "code", wrapped.status, + "method", req.Method, + "path", req.URL.EscapedPath(), + "duration", time.Since(start), + ) +} + +func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userHandler http.Handler) { + if isWebSocket(req) { + // WebSockets are not supported by the AssetServer + rw.WriteHeader(http.StatusNotImplemented) + return + } + + header := rw.Header() + // TODO: I don't think this is needed now? + //if a.servingFromDisk { + // header.Add(HeaderCacheControl, "no-cache") + //} + + reqPath := req.URL.Path + switch reqPath { + case "", "/", "/index.html": + // Cache the accept-language header + // before passing the request down the chain. + acceptLanguage := req.Header.Get(HeaderAcceptLanguage) + if acceptLanguage == "" { + acceptLanguage = "en" + } + + wrapped := &fallbackResponseWriter{ + rw: rw, + req: req, + fallback: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + // Set content type for default index.html + header.Set(HeaderContentType, "text/html; charset=utf-8") + a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage)) + }), + } + userHandler.ServeHTTP(wrapped, req) + + default: + // Check if the path matches a service route + for _, svc := range a.services { + if strings.HasPrefix(reqPath, svc.Route) { + req.URL.Path = strings.TrimPrefix(reqPath, svc.Route) + svc.Handler.ServeHTTP(rw, req) + return + } + } + + // Forward to the user-provided handler + userHandler.ServeHTTP(rw, req) + } +} + +func (a *AssetServer) AttachServiceHandler(route string, handler http.Handler) { + a.services = append(a.services, service{route, handler}) +} + +func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) { + err := ServeFile(rw, filename, blob) + if err != nil { + a.serveError(rw, err, "Error writing file content.", "filename", filename) + } +} + +func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) { + args = append(args, "error", err) + a.options.Logger.Error(msg, args...) + rw.WriteHeader(http.StatusInternalServerError) +} + +func GetStartURL(userURL string) (string, error) { + devServerURL := GetDevServerURL() + startURL := baseURL.String() + if devServerURL != "" { + // Parse the port + parsedURL, err := url.Parse(devServerURL) + if err != nil { + return "", fmt.Errorf("error parsing environment variable `FRONTEND_DEVSERVER_URL`: %w. Please check your `Taskfile.yml` file", err) + } + port := parsedURL.Port() + if port != "" { + baseURL.Host = net.JoinHostPort(baseURL.Hostname(), port) + startURL = baseURL.String() + } + } + + if userURL != "" { + parsedURL, err := baseURL.Parse(userURL) + if err != nil { + return "", fmt.Errorf("error parsing URL: %w", err) + } + + startURL = parsedURL.String() + } + + return startURL, nil +} diff --git a/v3/internal/assetserver/assetserver_darwin.go b/v3/internal/assetserver/assetserver_darwin.go new file mode 100644 index 000000000..faab164a4 --- /dev/null +++ b/v3/internal/assetserver/assetserver_darwin.go @@ -0,0 +1,8 @@ +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "wails", + Host: "localhost", +} diff --git a/v3/internal/assetserver/assetserver_dev.go b/v3/internal/assetserver/assetserver_dev.go new file mode 100644 index 000000000..e847ac480 --- /dev/null +++ b/v3/internal/assetserver/assetserver_dev.go @@ -0,0 +1,50 @@ +//go:build !production + +package assetserver + +import ( + "embed" + "io" + iofs "io/fs" +) + +//go:embed defaults +var defaultHTML embed.FS + +func defaultIndexHTML(language string) []byte { + result := []byte("index.html not found") + // Create an fs.Sub in the defaults directory + defaults, err := iofs.Sub(defaultHTML, "defaults") + if err != nil { + return result + } + // Get the 2 character language code + lang := "en" + if len(language) >= 2 { + lang = language[:2] + } + // Now we can read the index.html file in the format + // index..html. + + indexFile, err := defaults.Open("index." + lang + ".html") + if err != nil { + return result + } + + indexBytes, err := io.ReadAll(indexFile) + if err != nil { + return result + } + return indexBytes +} + +func (a *AssetServer) LogDetails() { + var info = []any{ + "middleware", a.options.Middleware != nil, + "handler", a.options.Handler != nil, + } + if devServerURL := GetDevServerURL(); devServerURL != "" { + info = append(info, "devServerURL", devServerURL) + } + a.options.Logger.Info("AssetServer Info:", info...) +} diff --git a/v3/internal/assetserver/assetserver_linux.go b/v3/internal/assetserver/assetserver_linux.go new file mode 100644 index 000000000..faab164a4 --- /dev/null +++ b/v3/internal/assetserver/assetserver_linux.go @@ -0,0 +1,8 @@ +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "wails", + Host: "localhost", +} diff --git a/v3/internal/assetserver/assetserver_production.go b/v3/internal/assetserver/assetserver_production.go new file mode 100644 index 000000000..f698fab40 --- /dev/null +++ b/v3/internal/assetserver/assetserver_production.go @@ -0,0 +1,9 @@ +//go:build production + +package assetserver + +func defaultIndexHTML(_ string) []byte { + return []byte("index.html not found") +} + +func (a *AssetServer) LogDetails() {} diff --git a/v3/internal/assetserver/assetserver_test.go b/v3/internal/assetserver/assetserver_test.go new file mode 100644 index 000000000..755ddf09c --- /dev/null +++ b/v3/internal/assetserver/assetserver_test.go @@ -0,0 +1,244 @@ +package assetserver + +import ( + "fmt" + "log/slog" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + _ "unsafe" + + "github.com/google/go-cmp/cmp" +) + +func TestContentSniffing(t *testing.T) { + longLead := strings.Repeat(" ", 512-6) + + tests := map[string]struct { + Expect string + Status int + Header map[string][]string + Body []string + }{ + "/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{"Hello!"}, + }, + "/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + "Hello!", + "", + }, + }, + "/lead/short/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{ + " " + "Hello!", + }, + }, + "/lead/short/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + " ", + "Hello!", + }, + }, + "/lead/long/simple": { + Expect: "text/html; charset=utf-8", + Body: []string{ + longLead + "Hello!", + }, + }, + "/lead/long/split": { + Expect: "text/html; charset=utf-8", + Body: []string{ + longLead, + "Hello!", + }, + }, + "/lead/toolong/simple": { + Expect: "text/plain; charset=utf-8", + Body: []string{ + "Hello" + longLead + "Hello!", + }, + }, + "/lead/toolong/split": { + Expect: "text/plain; charset=utf-8", + Body: []string{ + "Hello" + longLead, + "Hello!", + }, + }, + "/header": { + Expect: "text/html; charset=utf-8", + Status: http.StatusForbidden, + Header: map[string][]string{ + "X-Custom": {"CustomValue"}, + }, + Body: []string{"Hello!"}, + }, + "/custom": { + Expect: "text/plain;charset=utf-8", + Header: map[string][]string{ + "Content-Type": {"text/plain;charset=utf-8"}, + }, + Body: []string{"Hello!"}, + }, + } + + srv, err := NewAssetServer(&Options{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + test, ok := tests[r.URL.Path] + if !ok { + w.WriteHeader(http.StatusNotFound) + return + } + + for key, values := range test.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + if test.Status != 0 { + w.WriteHeader(test.Status) + } + + for _, chunk := range test.Body { + w.Write([]byte(chunk)) + } + }), + Logger: slog.Default(), + }) + if err != nil { + t.Fatal("AssetServer initialisation failed: ", err) + } + + for path, test := range tests { + t.Run(path[1:], func(t *testing.T) { + res := httptest.NewRecorder() + + req, err := http.NewRequest(http.MethodGet, path, nil) + if err != nil { + t.Fatal("http.NewRequest failed: ", err) + } + + srv.ServeHTTP(res, req) + + expectedStatus := http.StatusOK + if test.Status != 0 { + expectedStatus = test.Status + } + if res.Code != expectedStatus { + t.Errorf("Status code mismatch: want %d, got %d", expectedStatus, res.Code) + } + + if ct := res.Header().Get("Content-Type"); ct != test.Expect { + t.Errorf("Content type mismatch: want '%s', got '%s'", test.Expect, ct) + } + + for key, values := range test.Header { + if diff := cmp.Diff(values, res.Header().Values(key)); diff != "" { + t.Errorf("Header '%s' mismatch (-want +got):\n%s", key, diff) + } + } + + if diff := cmp.Diff(strings.Join(test.Body, ""), res.Body.String()); diff != "" { + t.Errorf("Response body mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestIndexFallback(t *testing.T) { + // Paths to try and whether a 404 should trigger a fallback. + paths := map[string]bool{ + "": true, + "/": true, + "/index": false, + "/index.html": true, + "/other": false, + } + + statuses := []int{ + http.StatusOK, + http.StatusNotFound, + http.StatusForbidden, + } + + header := map[string][]string{ + "X-Custom": {"CustomValue"}, + } + body := "Hello!" + + srv, err := NewAssetServer(&Options{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for key, values := range header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + status, err := strconv.Atoi(r.URL.Query().Get("status")) + if err == nil && status != 0 && status != http.StatusOK { + w.WriteHeader(status) + } + + w.Write([]byte(body)) + }), + Logger: slog.Default(), + }) + if err != nil { + t.Fatal("AssetServer initialisation failed: ", err) + } + + for path, fallback := range paths { + for _, status := range statuses { + key := "" + if len(path) > 0 { + key = path[1:] + } + + t.Run(fmt.Sprintf("%s/status=%d", key, status), func(t *testing.T) { + res := httptest.NewRecorder() + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s?status=%d", path, status), nil) + if err != nil { + t.Fatal("http.NewRequest failed: ", err) + } + + srv.ServeHTTP(res, req) + + fallbackTriggered := false + if status == http.StatusNotFound && fallback { + status = http.StatusOK + fallbackTriggered = true + } + + if res.Code != status { + t.Errorf("Status code mismatch: want %d, got %d", status, res.Code) + } + + if fallbackTriggered { + if cmp.Equal(body, res.Body.String()) { + t.Errorf("Fallback response has the same body as not found response") + } + return + } else { + for key, values := range header { + if diff := cmp.Diff(values, res.Header().Values(key)); diff != "" { + t.Errorf("Header '%s' mismatch (-want +got):\n%s", key, diff) + } + } + + if diff := cmp.Diff(body, res.Body.String()); diff != "" { + t.Errorf("Response body mismatch (-want +got):\n%s", diff) + } + } + }) + } + } +} diff --git a/v3/internal/assetserver/assetserver_webview.go b/v3/internal/assetserver/assetserver_webview.go new file mode 100644 index 000000000..0d029a34e --- /dev/null +++ b/v3/internal/assetserver/assetserver_webview.go @@ -0,0 +1,196 @@ +package assetserver + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" +) + +type assetServerWebView struct { + // ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed. + ExpectedWebViewHost string + + dispatchInit sync.Once + dispatchReqC chan<- webview.Request + dispatchWorkers int +} + +// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way. +func (a *AssetServer) ServeWebViewRequest(req webview.Request) { + a.dispatchInit.Do(func() { + workers := a.dispatchWorkers + if workers <= 0 { + return + } + + workerC := make(chan webview.Request, workers*2) + for i := 0; i < workers; i++ { + go func() { + for req := range workerC { + a.processWebViewRequest(req) + } + }() + } + + dispatchC := make(chan webview.Request) + go queueingDispatcher(50, dispatchC, workerC) + + a.dispatchReqC = dispatchC + }) + + if a.dispatchReqC == nil { + go a.processWebViewRequest(req) + } else { + a.dispatchReqC <- req + } +} + +func (a *AssetServer) processWebViewRequest(r webview.Request) { + uri, _ := r.URL() + a.processWebViewRequestInternal(r) + if err := r.Close(); err != nil { + a.options.Logger.Error("Unable to call close for request for uri.", "uri", uri) + } +} + +// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +func (a *AssetServer) processWebViewRequestInternal(r webview.Request) { + uri := "unknown" + var err error + + wrw := r.Response() + defer func() { + if err := wrw.Finish(); err != nil { + a.options.Logger.Error("Error finishing request.", "uri", uri, "error", err) + } + }() + + rw := newContentTypeSniffer(wrw) // Make sure we have a Content-Type sniffer + defer func() { + if _, err := rw.complete(); err != nil { + a.options.Logger.Error("Error writing response data.", "uri", uri, "error", err) + } + }() + defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status + + uri, err = r.URL() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("URL: %w", err)) + return + } + + method, err := r.Method() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err)) + return + } + + header, err := r.Header() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err)) + return + } + + body, err := r.Body() + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err)) + return + } + + if body == nil { + body = http.NoBody + } + defer body.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, uri, body) + if err != nil { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err)) + return + } + + // For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3) + req.URL.Scheme = "" + req.URL.Host = "" + req.URL.Fragment = "" + req.URL.RawFragment = "" + + if requestURL := req.URL; req.RequestURI == "" && requestURL != nil { + req.RequestURI = requestURL.String() + } + + req.Header = header + + if req.RemoteAddr == "" { + // 192.0.2.0/24 is "TEST-NET" in RFC 5737 + req.RemoteAddr = "192.0.2.1:1234" + } + + if req.RequestURI == "" && req.URL != nil { + req.RequestURI = req.URL.String() + } + + if req.ContentLength == 0 { + req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) + } else { + req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) + } + + if host := req.Header.Get(HeaderHost); host != "" { + req.Host = host + } + + if expectedHost := a.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host { + a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host)) + return + } + + a.ServeHTTP(rw, req) +} + +func (a *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) { + logInfo := uri + if uri, err := url.ParseRequestURI(uri); err == nil { + logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1) + } + + a.options.Logger.Error("Error processing request (HttpResponse=500)", "details", logInfo, "error", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) +} + +func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) { + q := newRingqueue[T](minQueueSize) + for { + in, ok := <-inC + if !ok { + return + } + + q.Add(in) + for q.Len() != 0 { + out, _ := q.Peek() + select { + case outC <- out: + q.Remove() + case in, ok := <-inC: + if !ok { + return + } + + q.Add(in) + } + } + } +} diff --git a/v3/internal/assetserver/assetserver_windows.go b/v3/internal/assetserver/assetserver_windows.go new file mode 100644 index 000000000..22deda4d2 --- /dev/null +++ b/v3/internal/assetserver/assetserver_windows.go @@ -0,0 +1,8 @@ +package assetserver + +import "net/url" + +var baseURL = url.URL{ + Scheme: "http", + Host: "wails.localhost", +} diff --git a/v3/internal/assetserver/build_dev.go b/v3/internal/assetserver/build_dev.go new file mode 100644 index 000000000..7747a7142 --- /dev/null +++ b/v3/internal/assetserver/build_dev.go @@ -0,0 +1,41 @@ +//go:build !production + +package assetserver + +import ( + _ "embed" + "io/fs" + "net/http" + "net/http/httputil" + "net/url" + "os" +) + +func NewAssetFileServer(vfs fs.FS) http.Handler { + devServerURL := GetDevServerURL() + if devServerURL == "" { + return newAssetFileServerFS(vfs) + } + + parsedURL, err := url.Parse(devServerURL) + if err != nil { + return http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + logError(req.Context(), "[ExternalAssetHandler] Invalid FRONTEND_DEVSERVER_URL. Should be valid URL", "error", err.Error()) + http.Error(rw, err.Error(), http.StatusInternalServerError) + }) + + } + + proxy := httputil.NewSingleHostReverseProxy(parsedURL) + proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { + logError(r.Context(), "[ExternalAssetHandler] Proxy error", "error", err.Error()) + rw.WriteHeader(http.StatusBadGateway) + } + + return proxy +} + +func GetDevServerURL() string { + return os.Getenv("FRONTEND_DEVSERVER_URL") +} diff --git a/v3/internal/assetserver/build_production.go b/v3/internal/assetserver/build_production.go new file mode 100644 index 000000000..98d5b4ffd --- /dev/null +++ b/v3/internal/assetserver/build_production.go @@ -0,0 +1,16 @@ +//go:build production + +package assetserver + +import ( + "io/fs" + "net/http" +) + +func NewAssetFileServer(vfs fs.FS) http.Handler { + return newAssetFileServerFS(vfs) +} + +func GetDevServerURL() string { + return "" +} diff --git a/v3/internal/assetserver/bundled_assetserver.go b/v3/internal/assetserver/bundled_assetserver.go new file mode 100644 index 000000000..15297cd37 --- /dev/null +++ b/v3/internal/assetserver/bundled_assetserver.go @@ -0,0 +1,33 @@ +package assetserver + +import ( + "github.com/wailsapp/wails/v3/internal/assetserver/bundledassets" + "io/fs" + "net/http" + "strings" +) + +type BundledAssetServer struct { + handler http.Handler +} + +func NewBundledAssetFileServer(fs fs.FS) *BundledAssetServer { + return &BundledAssetServer{ + handler: NewAssetFileServer(fs), + } +} + +func (b *BundledAssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if strings.HasPrefix(req.URL.Path, "/wails/") { + // Strip the /wails prefix + req.URL.Path = req.URL.Path[6:] + switch req.URL.Path { + case "/runtime.js": + rw.Header().Set("Content-Type", "application/javascript") + rw.Write([]byte(bundledassets.RuntimeJS)) + return + } + return + } + b.handler.ServeHTTP(rw, req) +} diff --git a/v3/internal/assetserver/bundledassets/runtime.debug.js b/v3/internal/assetserver/bundledassets/runtime.debug.js new file mode 100644 index 000000000..7de738a3d --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime.debug.js @@ -0,0 +1,2524 @@ +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; + +// desktop/@wailsio/runtime/src/index.ts +var index_exports = {}; +__export(index_exports, { + Application: () => application_exports, + Browser: () => browser_exports, + Call: () => calls_exports, + CancelError: () => CancelError, + CancellablePromise: () => CancellablePromise, + CancelledRejectionError: () => CancelledRejectionError, + Clipboard: () => clipboard_exports, + Create: () => create_exports, + Dialogs: () => dialogs_exports, + Events: () => events_exports, + Flags: () => flags_exports, + Screens: () => screens_exports, + System: () => system_exports, + WML: () => wml_exports, + Window: () => window_default +}); + +// desktop/@wailsio/runtime/src/wml.ts +var wml_exports = {}; +__export(wml_exports, { + Enable: () => Enable, + Reload: () => Reload +}); + +// desktop/@wailsio/runtime/src/browser.ts +var browser_exports = {}; +__export(browser_exports, { + OpenURL: () => OpenURL +}); + +// desktop/@wailsio/runtime/src/nanoid.ts +var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; +function nanoid(size = 21) { + let id = ""; + let i = size | 0; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; +} + +// desktop/@wailsio/runtime/src/runtime.ts +var runtimeURL = window.location.origin + "/wails/runtime"; +var objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10 +}); +var clientId = nanoid(); +function newRuntimeCaller(object, windowName = "") { + return function(method, args = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} +async function runtimeCallWithID(objectID, method, windowName, args) { + var _a2, _b; + let url = new URL(runtimeURL); + url.searchParams.append("object", objectID.toString()); + url.searchParams.append("method", method.toString()); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let headers = { + ["x-wails-client-id"]: clientId + }; + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + let response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(await response.text()); + } + if (((_b = (_a2 = response.headers.get("Content-Type")) == null ? void 0 : _a2.indexOf("application/json")) != null ? _b : -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} + +// desktop/@wailsio/runtime/src/browser.ts +var call = newRuntimeCaller(objectNames.Browser); +var BrowserOpenURL = 0; +function OpenURL(url) { + return call(BrowserOpenURL, { url: url.toString() }); +} + +// desktop/@wailsio/runtime/src/dialogs.ts +var dialogs_exports = {}; +__export(dialogs_exports, { + Error: () => Error2, + Info: () => Info, + OpenFile: () => OpenFile, + Question: () => Question, + SaveFile: () => SaveFile, + Warning: () => Warning +}); +window._wails = window._wails || {}; +window._wails.dialogErrorCallback = dialogErrorCallback; +window._wails.dialogResultCallback = dialogResultCallback; +var call2 = newRuntimeCaller(objectNames.Dialog); +var dialogResponses = /* @__PURE__ */ new Map(); +var DialogInfo = 0; +var DialogWarning = 1; +var DialogError = 2; +var DialogQuestion = 3; +var DialogOpenFile = 4; +var DialogSaveFile = 5; +function dialogResultCallback(id, data, isJSON) { + let resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + if (isJSON) { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } else { + resolvers.resolve(data); + } +} +function dialogErrorCallback(id, message) { + var _a2; + (_a2 = getAndDeleteResponse(id)) == null ? void 0 : _a2.reject(new window.Error(message)); +} +function getAndDeleteResponse(id) { + const response = dialogResponses.get(id); + dialogResponses.delete(id); + return response; +} +function generateID() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; +} +function dialog(type, options = {}) { + const id = generateID(); + return new Promise((resolve, reject) => { + dialogResponses.set(id, { resolve, reject }); + call2(type, Object.assign({ "dialog-id": id }, options)).catch((err) => { + dialogResponses.delete(id); + reject(err); + }); + }); +} +function Info(options) { + return dialog(DialogInfo, options); +} +function Warning(options) { + return dialog(DialogWarning, options); +} +function Error2(options) { + return dialog(DialogError, options); +} +function Question(options) { + return dialog(DialogQuestion, options); +} +function OpenFile(options) { + var _a2; + return (_a2 = dialog(DialogOpenFile, options)) != null ? _a2 : []; +} +function SaveFile(options) { + return dialog(DialogSaveFile, options); +} + +// desktop/@wailsio/runtime/src/events.ts +var events_exports = {}; +__export(events_exports, { + Emit: () => Emit, + Off: () => Off, + OffAll: () => OffAll, + On: () => On, + OnMultiple: () => OnMultiple, + Once: () => Once, + Types: () => Types, + WailsEvent: () => WailsEvent +}); + +// desktop/@wailsio/runtime/src/listener.ts +var eventListeners = /* @__PURE__ */ new Map(); +var Listener = class { + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + dispatch(data) { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + if (this.maxCallbacks === -1) return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +}; +function listenerOff(listener) { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + listeners = listeners.filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} + +// desktop/@wailsio/runtime/src/event_types.ts +var Types = Object.freeze({ + Windows: Object.freeze({ + APMPowerSettingChange: "windows:APMPowerSettingChange", + APMPowerStatusChange: "windows:APMPowerStatusChange", + APMResumeAutomatic: "windows:APMResumeAutomatic", + APMResumeSuspend: "windows:APMResumeSuspend", + APMSuspend: "windows:APMSuspend", + ApplicationStarted: "windows:ApplicationStarted", + SystemThemeChanged: "windows:SystemThemeChanged", + WebViewNavigationCompleted: "windows:WebViewNavigationCompleted", + WindowActive: "windows:WindowActive", + WindowBackgroundErase: "windows:WindowBackgroundErase", + WindowClickActive: "windows:WindowClickActive", + WindowClosing: "windows:WindowClosing", + WindowDidMove: "windows:WindowDidMove", + WindowDidResize: "windows:WindowDidResize", + WindowDPIChanged: "windows:WindowDPIChanged", + WindowDragDrop: "windows:WindowDragDrop", + WindowDragEnter: "windows:WindowDragEnter", + WindowDragLeave: "windows:WindowDragLeave", + WindowDragOver: "windows:WindowDragOver", + WindowEndMove: "windows:WindowEndMove", + WindowEndResize: "windows:WindowEndResize", + WindowFullscreen: "windows:WindowFullscreen", + WindowHide: "windows:WindowHide", + WindowInactive: "windows:WindowInactive", + WindowKeyDown: "windows:WindowKeyDown", + WindowKeyUp: "windows:WindowKeyUp", + WindowKillFocus: "windows:WindowKillFocus", + WindowNonClientHit: "windows:WindowNonClientHit", + WindowNonClientMouseDown: "windows:WindowNonClientMouseDown", + WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave", + WindowNonClientMouseMove: "windows:WindowNonClientMouseMove", + WindowNonClientMouseUp: "windows:WindowNonClientMouseUp", + WindowPaint: "windows:WindowPaint", + WindowRestore: "windows:WindowRestore", + WindowSetFocus: "windows:WindowSetFocus", + WindowShow: "windows:WindowShow", + WindowStartMove: "windows:WindowStartMove", + WindowStartResize: "windows:WindowStartResize", + WindowUnFullscreen: "windows:WindowUnFullscreen", + WindowZOrderChanged: "windows:WindowZOrderChanged", + WindowMinimise: "windows:WindowMinimise", + WindowUnMinimise: "windows:WindowUnMinimise", + WindowMaximise: "windows:WindowMaximise", + WindowUnMaximise: "windows:WindowUnMaximise" + }), + Mac: Object.freeze({ + ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive", + ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties", + ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance", + ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon", + ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState", + ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters", + ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame", + ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation", + ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme", + ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching", + ApplicationDidHide: "mac:ApplicationDidHide", + ApplicationDidResignActive: "mac:ApplicationDidResignActive", + ApplicationDidUnhide: "mac:ApplicationDidUnhide", + ApplicationDidUpdate: "mac:ApplicationDidUpdate", + ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen", + ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive", + ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching", + ApplicationWillHide: "mac:ApplicationWillHide", + ApplicationWillResignActive: "mac:ApplicationWillResignActive", + ApplicationWillTerminate: "mac:ApplicationWillTerminate", + ApplicationWillUnhide: "mac:ApplicationWillUnhide", + ApplicationWillUpdate: "mac:ApplicationWillUpdate", + MenuDidAddItem: "mac:MenuDidAddItem", + MenuDidBeginTracking: "mac:MenuDidBeginTracking", + MenuDidClose: "mac:MenuDidClose", + MenuDidDisplayItem: "mac:MenuDidDisplayItem", + MenuDidEndTracking: "mac:MenuDidEndTracking", + MenuDidHighlightItem: "mac:MenuDidHighlightItem", + MenuDidOpen: "mac:MenuDidOpen", + MenuDidPopUp: "mac:MenuDidPopUp", + MenuDidRemoveItem: "mac:MenuDidRemoveItem", + MenuDidSendAction: "mac:MenuDidSendAction", + MenuDidSendActionToItem: "mac:MenuDidSendActionToItem", + MenuDidUpdate: "mac:MenuDidUpdate", + MenuWillAddItem: "mac:MenuWillAddItem", + MenuWillBeginTracking: "mac:MenuWillBeginTracking", + MenuWillDisplayItem: "mac:MenuWillDisplayItem", + MenuWillEndTracking: "mac:MenuWillEndTracking", + MenuWillHighlightItem: "mac:MenuWillHighlightItem", + MenuWillOpen: "mac:MenuWillOpen", + MenuWillPopUp: "mac:MenuWillPopUp", + MenuWillRemoveItem: "mac:MenuWillRemoveItem", + MenuWillSendAction: "mac:MenuWillSendAction", + MenuWillSendActionToItem: "mac:MenuWillSendActionToItem", + MenuWillUpdate: "mac:MenuWillUpdate", + WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation", + WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation", + WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation", + WindowDidBecomeKey: "mac:WindowDidBecomeKey", + WindowDidBecomeMain: "mac:WindowDidBecomeMain", + WindowDidBeginSheet: "mac:WindowDidBeginSheet", + WindowDidChangeAlpha: "mac:WindowDidChangeAlpha", + WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation", + WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties", + WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior", + WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance", + WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState", + WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode", + WindowDidChangeScreen: "mac:WindowDidChangeScreen", + WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters", + WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile", + WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace", + WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties", + WindowDidChangeSharingType: "mac:WindowDidChangeSharingType", + WindowDidChangeSpace: "mac:WindowDidChangeSpace", + WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode", + WindowDidChangeTitle: "mac:WindowDidChangeTitle", + WindowDidChangeToolbar: "mac:WindowDidChangeToolbar", + WindowDidDeminiaturize: "mac:WindowDidDeminiaturize", + WindowDidEndSheet: "mac:WindowDidEndSheet", + WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen", + WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser", + WindowDidExitFullScreen: "mac:WindowDidExitFullScreen", + WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser", + WindowDidExpose: "mac:WindowDidExpose", + WindowDidFocus: "mac:WindowDidFocus", + WindowDidMiniaturize: "mac:WindowDidMiniaturize", + WindowDidMove: "mac:WindowDidMove", + WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen", + WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen", + WindowDidResignKey: "mac:WindowDidResignKey", + WindowDidResignMain: "mac:WindowDidResignMain", + WindowDidResize: "mac:WindowDidResize", + WindowDidUpdate: "mac:WindowDidUpdate", + WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha", + WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior", + WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties", + WindowDidUpdateShadow: "mac:WindowDidUpdateShadow", + WindowDidUpdateTitle: "mac:WindowDidUpdateTitle", + WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar", + WindowDidZoom: "mac:WindowDidZoom", + WindowFileDraggingEntered: "mac:WindowFileDraggingEntered", + WindowFileDraggingExited: "mac:WindowFileDraggingExited", + WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed", + WindowHide: "mac:WindowHide", + WindowMaximise: "mac:WindowMaximise", + WindowUnMaximise: "mac:WindowUnMaximise", + WindowMinimise: "mac:WindowMinimise", + WindowUnMinimise: "mac:WindowUnMinimise", + WindowShouldClose: "mac:WindowShouldClose", + WindowShow: "mac:WindowShow", + WindowWillBecomeKey: "mac:WindowWillBecomeKey", + WindowWillBecomeMain: "mac:WindowWillBecomeMain", + WindowWillBeginSheet: "mac:WindowWillBeginSheet", + WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode", + WindowWillClose: "mac:WindowWillClose", + WindowWillDeminiaturize: "mac:WindowWillDeminiaturize", + WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen", + WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser", + WindowWillExitFullScreen: "mac:WindowWillExitFullScreen", + WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser", + WindowWillFocus: "mac:WindowWillFocus", + WindowWillMiniaturize: "mac:WindowWillMiniaturize", + WindowWillMove: "mac:WindowWillMove", + WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen", + WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen", + WindowWillResignMain: "mac:WindowWillResignMain", + WindowWillResize: "mac:WindowWillResize", + WindowWillUnfocus: "mac:WindowWillUnfocus", + WindowWillUpdate: "mac:WindowWillUpdate", + WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha", + WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior", + WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties", + WindowWillUpdateShadow: "mac:WindowWillUpdateShadow", + WindowWillUpdateTitle: "mac:WindowWillUpdateTitle", + WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar", + WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility", + WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame", + WindowZoomIn: "mac:WindowZoomIn", + WindowZoomOut: "mac:WindowZoomOut", + WindowZoomReset: "mac:WindowZoomReset" + }), + Linux: Object.freeze({ + ApplicationStartup: "linux:ApplicationStartup", + SystemThemeChanged: "linux:SystemThemeChanged", + WindowDeleteEvent: "linux:WindowDeleteEvent", + WindowDidMove: "linux:WindowDidMove", + WindowDidResize: "linux:WindowDidResize", + WindowFocusIn: "linux:WindowFocusIn", + WindowFocusOut: "linux:WindowFocusOut", + WindowLoadChanged: "linux:WindowLoadChanged" + }), + Common: Object.freeze({ + ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile", + ApplicationStarted: "common:ApplicationStarted", + ThemeChanged: "common:ThemeChanged", + WindowClosing: "common:WindowClosing", + WindowDidMove: "common:WindowDidMove", + WindowDidResize: "common:WindowDidResize", + WindowDPIChanged: "common:WindowDPIChanged", + WindowFilesDropped: "common:WindowFilesDropped", + WindowFocus: "common:WindowFocus", + WindowFullscreen: "common:WindowFullscreen", + WindowHide: "common:WindowHide", + WindowLostFocus: "common:WindowLostFocus", + WindowMaximise: "common:WindowMaximise", + WindowMinimise: "common:WindowMinimise", + WindowToggleFrameless: "common:WindowToggleFrameless", + WindowRestore: "common:WindowRestore", + WindowRuntimeReady: "common:WindowRuntimeReady", + WindowShow: "common:WindowShow", + WindowUnFullscreen: "common:WindowUnFullscreen", + WindowUnMaximise: "common:WindowUnMaximise", + WindowUnMinimise: "common:WindowUnMinimise", + WindowZoom: "common:WindowZoom", + WindowZoomIn: "common:WindowZoomIn", + WindowZoomOut: "common:WindowZoomOut", + WindowZoomReset: "common:WindowZoomReset" + }) +}); + +// desktop/@wailsio/runtime/src/events.ts +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; +var call3 = newRuntimeCaller(objectNames.Events); +var EmitMethod = 0; +var WailsEvent = class { + constructor(name, data = null) { + this.name = name; + this.data = data; + } +}; +function dispatchWailsEvent(event) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + let wailsEvent = new WailsEvent(event.name, event.data); + if ("sender" in event) { + wailsEvent.sender = event.sender; + } + listeners = listeners.filter((listener) => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} +function OnMultiple(eventName, callback, maxCallbacks) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} +function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} +function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); +} +function Off(...eventNames) { + eventNames.forEach((eventName) => eventListeners.delete(eventName)); +} +function OffAll() { + eventListeners.clear(); +} +function Emit(name, data) { + let event; + if (typeof name === "object" && name !== null && "name" in name && "data" in name) { + event = new WailsEvent(name["name"], name["data"]); + } else { + event = new WailsEvent(name, data); + } + return call3(EmitMethod, event); +} + +// desktop/@wailsio/runtime/src/utils.ts +function debugLog(message) { + console.log( + "%c wails3 %c " + message + " ", + "background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem", + "background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem" + ); +} +function canTrackButtons() { + return new MouseEvent("mousedown").buttons === 0; +} +function canAbortListeners() { + if (!EventTarget || !AbortSignal || !AbortController) + return false; + let result = true; + const target = new EventTarget(); + const controller = new AbortController(); + target.addEventListener("test", () => { + result = false; + }, { signal: controller.signal }); + controller.abort(); + target.dispatchEvent(new CustomEvent("test")); + return result; +} +function eventTarget(event) { + var _a2; + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return (_a2 = event.target.parentElement) != null ? _a2 : document.body; + } else { + return document.body; + } +} +var isReady = false; +document.addEventListener("DOMContentLoaded", () => { + isReady = true; +}); +function whenReady(callback) { + if (isReady || document.readyState === "complete") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +} + +// desktop/@wailsio/runtime/src/window.ts +var PositionMethod = 0; +var CenterMethod = 1; +var CloseMethod = 2; +var DisableSizeConstraintsMethod = 3; +var EnableSizeConstraintsMethod = 4; +var FocusMethod = 5; +var ForceReloadMethod = 6; +var FullscreenMethod = 7; +var GetScreenMethod = 8; +var GetZoomMethod = 9; +var HeightMethod = 10; +var HideMethod = 11; +var IsFocusedMethod = 12; +var IsFullscreenMethod = 13; +var IsMaximisedMethod = 14; +var IsMinimisedMethod = 15; +var MaximiseMethod = 16; +var MinimiseMethod = 17; +var NameMethod = 18; +var OpenDevToolsMethod = 19; +var RelativePositionMethod = 20; +var ReloadMethod = 21; +var ResizableMethod = 22; +var RestoreMethod = 23; +var SetPositionMethod = 24; +var SetAlwaysOnTopMethod = 25; +var SetBackgroundColourMethod = 26; +var SetFramelessMethod = 27; +var SetFullscreenButtonEnabledMethod = 28; +var SetMaxSizeMethod = 29; +var SetMinSizeMethod = 30; +var SetRelativePositionMethod = 31; +var SetResizableMethod = 32; +var SetSizeMethod = 33; +var SetTitleMethod = 34; +var SetZoomMethod = 35; +var ShowMethod = 36; +var SizeMethod = 37; +var ToggleFullscreenMethod = 38; +var ToggleMaximiseMethod = 39; +var ToggleFramelessMethod = 40; +var UnFullscreenMethod = 41; +var UnMaximiseMethod = 42; +var UnMinimiseMethod = 43; +var WidthMethod = 44; +var ZoomMethod = 45; +var ZoomInMethod = 46; +var ZoomOutMethod = 47; +var ZoomResetMethod = 48; +var callerSym = Symbol("caller"); +callerSym; +var _Window = class _Window { + /** + * Initialises a window object with the specified name. + * + * @private + * @param name - The name of the target window. + */ + constructor(name = "") { + this[callerSym] = newRuntimeCaller(objectNames.Window, name); + for (const method of Object.getOwnPropertyNames(_Window.prototype)) { + if (method !== "constructor" && typeof this[method] === "function") { + this[method] = this[method].bind(this); + } + } + } + /** + * Gets the specified window. + * + * @param name - The name of the window to get. + * @returns The corresponding window object. + */ + Get(name) { + return new _Window(name); + } + /** + * Returns the absolute position of the window. + * + * @returns The current absolute position of the window. + */ + Position() { + return this[callerSym](PositionMethod); + } + /** + * Centers the window on the screen. + */ + Center() { + return this[callerSym](CenterMethod); + } + /** + * Closes the window. + */ + Close() { + return this[callerSym](CloseMethod); + } + /** + * Disables min/max size constraints. + */ + DisableSizeConstraints() { + return this[callerSym](DisableSizeConstraintsMethod); + } + /** + * Enables min/max size constraints. + */ + EnableSizeConstraints() { + return this[callerSym](EnableSizeConstraintsMethod); + } + /** + * Focuses the window. + */ + Focus() { + return this[callerSym](FocusMethod); + } + /** + * Forces the window to reload the page assets. + */ + ForceReload() { + return this[callerSym](ForceReloadMethod); + } + /** + * Switches the window to fullscreen mode. + */ + Fullscreen() { + return this[callerSym](FullscreenMethod); + } + /** + * Returns the screen that the window is on. + * + * @returns The screen the window is currently on. + */ + GetScreen() { + return this[callerSym](GetScreenMethod); + } + /** + * Returns the current zoom level of the window. + * + * @returns The current zoom level. + */ + GetZoom() { + return this[callerSym](GetZoomMethod); + } + /** + * Returns the height of the window. + * + * @returns The current height of the window. + */ + Height() { + return this[callerSym](HeightMethod); + } + /** + * Hides the window. + */ + Hide() { + return this[callerSym](HideMethod); + } + /** + * Returns true if the window is focused. + * + * @returns Whether the window is currently focused. + */ + IsFocused() { + return this[callerSym](IsFocusedMethod); + } + /** + * Returns true if the window is fullscreen. + * + * @returns Whether the window is currently fullscreen. + */ + IsFullscreen() { + return this[callerSym](IsFullscreenMethod); + } + /** + * Returns true if the window is maximised. + * + * @returns Whether the window is currently maximised. + */ + IsMaximised() { + return this[callerSym](IsMaximisedMethod); + } + /** + * Returns true if the window is minimised. + * + * @returns Whether the window is currently minimised. + */ + IsMinimised() { + return this[callerSym](IsMinimisedMethod); + } + /** + * Maximises the window. + */ + Maximise() { + return this[callerSym](MaximiseMethod); + } + /** + * Minimises the window. + */ + Minimise() { + return this[callerSym](MinimiseMethod); + } + /** + * Returns the name of the window. + * + * @returns The name of the window. + */ + Name() { + return this[callerSym](NameMethod); + } + /** + * Opens the development tools pane. + */ + OpenDevTools() { + return this[callerSym](OpenDevToolsMethod); + } + /** + * Returns the relative position of the window to the screen. + * + * @returns The current relative position of the window. + */ + RelativePosition() { + return this[callerSym](RelativePositionMethod); + } + /** + * Reloads the page assets. + */ + Reload() { + return this[callerSym](ReloadMethod); + } + /** + * Returns true if the window is resizable. + * + * @returns Whether the window is currently resizable. + */ + Resizable() { + return this[callerSym](ResizableMethod); + } + /** + * Restores the window to its previous state if it was previously minimised, maximised or fullscreen. + */ + Restore() { + return this[callerSym](RestoreMethod); + } + /** + * Sets the absolute position of the window. + * + * @param x - The desired horizontal absolute position of the window. + * @param y - The desired vertical absolute position of the window. + */ + SetPosition(x, y) { + return this[callerSym](SetPositionMethod, { x, y }); + } + /** + * Sets the window to be always on top. + * + * @param alwaysOnTop - Whether the window should stay on top. + */ + SetAlwaysOnTop(alwaysOnTop) { + return this[callerSym](SetAlwaysOnTopMethod, { alwaysOnTop }); + } + /** + * Sets the background colour of the window. + * + * @param r - The desired red component of the window background. + * @param g - The desired green component of the window background. + * @param b - The desired blue component of the window background. + * @param a - The desired alpha component of the window background. + */ + SetBackgroundColour(r, g, b, a) { + return this[callerSym](SetBackgroundColourMethod, { r, g, b, a }); + } + /** + * Removes the window frame and title bar. + * + * @param frameless - Whether the window should be frameless. + */ + SetFrameless(frameless) { + return this[callerSym](SetFramelessMethod, { frameless }); + } + /** + * Disables the system fullscreen button. + * + * @param enabled - Whether the fullscreen button should be enabled. + */ + SetFullscreenButtonEnabled(enabled) { + return this[callerSym](SetFullscreenButtonEnabledMethod, { enabled }); + } + /** + * Sets the maximum size of the window. + * + * @param width - The desired maximum width of the window. + * @param height - The desired maximum height of the window. + */ + SetMaxSize(width, height) { + return this[callerSym](SetMaxSizeMethod, { width, height }); + } + /** + * Sets the minimum size of the window. + * + * @param width - The desired minimum width of the window. + * @param height - The desired minimum height of the window. + */ + SetMinSize(width, height) { + return this[callerSym](SetMinSizeMethod, { width, height }); + } + /** + * Sets the relative position of the window to the screen. + * + * @param x - The desired horizontal relative position of the window. + * @param y - The desired vertical relative position of the window. + */ + SetRelativePosition(x, y) { + return this[callerSym](SetRelativePositionMethod, { x, y }); + } + /** + * Sets whether the window is resizable. + * + * @param resizable - Whether the window should be resizable. + */ + SetResizable(resizable2) { + return this[callerSym](SetResizableMethod, { resizable: resizable2 }); + } + /** + * Sets the size of the window. + * + * @param width - The desired width of the window. + * @param height - The desired height of the window. + */ + SetSize(width, height) { + return this[callerSym](SetSizeMethod, { width, height }); + } + /** + * Sets the title of the window. + * + * @param title - The desired title of the window. + */ + SetTitle(title) { + return this[callerSym](SetTitleMethod, { title }); + } + /** + * Sets the zoom level of the window. + * + * @param zoom - The desired zoom level. + */ + SetZoom(zoom) { + return this[callerSym](SetZoomMethod, { zoom }); + } + /** + * Shows the window. + */ + Show() { + return this[callerSym](ShowMethod); + } + /** + * Returns the size of the window. + * + * @returns The current size of the window. + */ + Size() { + return this[callerSym](SizeMethod); + } + /** + * Toggles the window between fullscreen and normal. + */ + ToggleFullscreen() { + return this[callerSym](ToggleFullscreenMethod); + } + /** + * Toggles the window between maximised and normal. + */ + ToggleMaximise() { + return this[callerSym](ToggleMaximiseMethod); + } + /** + * Toggles the window between frameless and normal. + */ + ToggleFrameless() { + return this[callerSym](ToggleFramelessMethod); + } + /** + * Un-fullscreens the window. + */ + UnFullscreen() { + return this[callerSym](UnFullscreenMethod); + } + /** + * Un-maximises the window. + */ + UnMaximise() { + return this[callerSym](UnMaximiseMethod); + } + /** + * Un-minimises the window. + */ + UnMinimise() { + return this[callerSym](UnMinimiseMethod); + } + /** + * Returns the width of the window. + * + * @returns The current width of the window. + */ + Width() { + return this[callerSym](WidthMethod); + } + /** + * Zooms the window. + */ + Zoom() { + return this[callerSym](ZoomMethod); + } + /** + * Increases the zoom level of the webview content. + */ + ZoomIn() { + return this[callerSym](ZoomInMethod); + } + /** + * Decreases the zoom level of the webview content. + */ + ZoomOut() { + return this[callerSym](ZoomOutMethod); + } + /** + * Resets the zoom level of the webview content. + */ + ZoomReset() { + return this[callerSym](ZoomResetMethod); + } +}; +var Window = _Window; +var thisWindow = new Window(""); +var window_default = thisWindow; + +// desktop/@wailsio/runtime/src/wml.ts +function sendEvent(eventName, data = null) { + void Emit(eventName, data); +} +function callWindowMethod(windowName, methodName) { + const targetWindow = window_default.Get(windowName); + const method = targetWindow[methodName]; + if (typeof method !== "function") { + console.error("Window method '".concat(methodName, "' not found")); + return; + } + try { + method.call(targetWindow); + } catch (e) { + console.error("Error calling window method '".concat(methodName, "': "), e); + } +} +function onWMLTriggered(ev) { + const element = ev.currentTarget; + function runEffect(choice = "Yes") { + if (choice !== "Yes") + return; + const eventType = element.getAttribute("wml-event") || element.getAttribute("data-wml-event"); + const targetWindow = element.getAttribute("wml-target-window") || element.getAttribute("data-wml-target-window") || ""; + const windowMethod = element.getAttribute("wml-window") || element.getAttribute("data-wml-window"); + const url = element.getAttribute("wml-openurl") || element.getAttribute("data-wml-openurl"); + if (eventType !== null) + sendEvent(eventType); + if (windowMethod !== null) + callWindowMethod(targetWindow, windowMethod); + if (url !== null) + void OpenURL(url); + } + const confirm = element.getAttribute("wml-confirm") || element.getAttribute("data-wml-confirm"); + if (confirm) { + Question({ + Title: "Confirm", + Message: confirm, + Detached: false, + Buttons: [ + { Label: "Yes" }, + { Label: "No", IsDefault: true } + ] + }).then(runEffect); + } else { + runEffect(); + } +} +var controllerSym = Symbol("controller"); +var triggerMapSym = Symbol("triggerMap"); +var elementCountSym = Symbol("elementCount"); +controllerSym; +var AbortControllerRegistry = class { + constructor() { + this[controllerSym] = new AbortController(); + } + /** + * Returns an options object for addEventListener that ties the listener + * to the AbortSignal from the current AbortController. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified elements + */ + set(element, triggers) { + return { signal: this[controllerSym].signal }; + } + /** + * Removes all registered event listeners and resets the registry. + */ + reset() { + this[controllerSym].abort(); + this[controllerSym] = new AbortController(); + } +}; +triggerMapSym, elementCountSym; +var WeakMapRegistry = class { + constructor() { + this[triggerMapSym] = /* @__PURE__ */ new WeakMap(); + this[elementCountSym] = 0; + } + /** + * Sets active triggers for the specified element. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified element + */ + set(element, triggers) { + if (!this[triggerMapSym].has(element)) { + this[elementCountSym]++; + } + this[triggerMapSym].set(element, triggers); + return {}; + } + /** + * Removes all registered event listeners. + */ + reset() { + if (this[elementCountSym] <= 0) + return; + for (const element of document.body.querySelectorAll("*")) { + if (this[elementCountSym] <= 0) + break; + const triggers = this[triggerMapSym].get(element); + if (triggers != null) { + this[elementCountSym]--; + } + for (const trigger of triggers || []) + element.removeEventListener(trigger, onWMLTriggered); + } + this[triggerMapSym] = /* @__PURE__ */ new WeakMap(); + this[elementCountSym] = 0; + } +}; +var triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry(); +function addWMLListeners(element) { + const triggerRegExp = /\S+/g; + const triggerAttr = element.getAttribute("wml-trigger") || element.getAttribute("data-wml-trigger") || "click"; + const triggers = []; + let match; + while ((match = triggerRegExp.exec(triggerAttr)) !== null) + triggers.push(match[0]); + const options = triggerRegistry.set(element, triggers); + for (const trigger of triggers) + element.addEventListener(trigger, onWMLTriggered, options); +} +function Enable() { + whenReady(Reload); +} +function Reload() { + triggerRegistry.reset(); + document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(addWMLListeners); +} + +// desktop/compiled/main.js +window.wails = index_exports; +Enable(); +if (true) { + debugLog("Wails Runtime Loaded"); +} + +// desktop/@wailsio/runtime/src/system.ts +var system_exports = {}; +__export(system_exports, { + Capabilities: () => Capabilities, + Environment: () => Environment, + IsAMD64: () => IsAMD64, + IsARM: () => IsARM, + IsARM64: () => IsARM64, + IsDarkMode: () => IsDarkMode, + IsDebug: () => IsDebug, + IsLinux: () => IsLinux, + IsMac: () => IsMac, + IsWindows: () => IsWindows, + invoke: () => invoke +}); +var call4 = newRuntimeCaller(objectNames.System); +var SystemIsDarkMode = 0; +var SystemEnvironment = 1; +var _invoke = function() { + var _a2, _b, _c, _d, _e; + try { + if ((_b = (_a2 = window.chrome) == null ? void 0 : _a2.webview) == null ? void 0 : _b.postMessage) { + return window.chrome.webview.postMessage.bind(window.chrome.webview); + } else if ((_e = (_d = (_c = window.webkit) == null ? void 0 : _c.messageHandlers) == null ? void 0 : _d["external"]) == null ? void 0 : _e.postMessage) { + return window.webkit.messageHandlers["external"].postMessage.bind(window.webkit.messageHandlers["external"]); + } + } catch (e) { + } + console.warn( + "\n%c\u26A0\uFE0F Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n", + "background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;", + "background: transparent;", + "color: #ffffff; font-style: italic; font-weight: bold;" + ); + return null; +}(); +function invoke(msg) { + _invoke == null ? void 0 : _invoke(msg); +} +function IsDarkMode() { + return call4(SystemIsDarkMode); +} +async function Capabilities() { + let response = await fetch("/wails/capabilities"); + if (response.ok) { + return response.json(); + } else { + throw new Error("could not fetch capabilities: " + response.statusText); + } +} +function Environment() { + return call4(SystemEnvironment); +} +function IsWindows() { + return window._wails.environment.OS === "windows"; +} +function IsLinux() { + return window._wails.environment.OS === "linux"; +} +function IsMac() { + return window._wails.environment.OS === "darwin"; +} +function IsAMD64() { + return window._wails.environment.Arch === "amd64"; +} +function IsARM() { + return window._wails.environment.Arch === "arm"; +} +function IsARM64() { + return window._wails.environment.Arch === "arm64"; +} +function IsDebug() { + return Boolean(window._wails.environment.Debug); +} + +// desktop/@wailsio/runtime/src/contextmenu.ts +window.addEventListener("contextmenu", contextMenuHandler); +var call5 = newRuntimeCaller(objectNames.ContextMenu); +var ContextMenuOpen = 0; +function openContextMenu(id, x, y, data) { + void call5(ContextMenuOpen, { id, x, y, data }); +} +function contextMenuHandler(event) { + const target = eventTarget(event); + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} +function processDefaultContextMenu(event, target) { + if (IsDebug()) { + return; + } + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case "show": + return; + case "hide": + event.preventDefault(); + return; + } + if (target.isContentEditable) { + return; + } + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || !target.readOnly && !target.disabled) { + return; + } + } + event.preventDefault(); +} + +// desktop/@wailsio/runtime/src/flags.ts +var flags_exports = {}; +__export(flags_exports, { + GetFlag: () => GetFlag +}); +function GetFlag(key) { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} + +// desktop/@wailsio/runtime/src/drag.ts +var canDrag = false; +var dragging = false; +var resizable = false; +var canResize = false; +var resizing = false; +var resizeEdge = ""; +var defaultCursor = "auto"; +var buttons = 0; +var buttonsTracked = canTrackButtons(); +window._wails = window._wails || {}; +window._wails.setResizable = (value) => { + resizable = value; + if (!resizable) { + canResize = resizing = false; + setResize(); + } +}; +window.addEventListener("mousedown", update, { capture: true }); +window.addEventListener("mousemove", update, { capture: true }); +window.addEventListener("mouseup", update, { capture: true }); +for (const ev of ["click", "contextmenu", "dblclick"]) { + window.addEventListener(ev, suppressEvent, { capture: true }); +} +function suppressEvent(event) { + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} +var MouseDown = 0; +var MouseUp = 1; +var MouseMove = 2; +function update(event) { + let eventType, eventButtons = event.buttons; + switch (event.type) { + case "mousedown": + eventType = MouseDown; + if (!buttonsTracked) { + eventButtons = buttons | 1 << event.button; + } + break; + case "mouseup": + eventType = MouseUp; + if (!buttonsTracked) { + eventButtons = buttons & ~(1 << event.button); + } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { + eventButtons = buttons; + } + break; + } + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + buttons = eventButtons; + if (eventType === MouseDown && !(pressed & event.button)) { + released |= 1 << event.button; + pressed |= 1 << event.button; + } + if (eventType !== MouseMove && resizing || dragging && (eventType === MouseDown || event.button !== 0)) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + if (released & 1) { + primaryUp(event); + } + if (pressed & 1) { + primaryDown(event); + } + if (eventType === MouseMove) { + onMouseMove(event); + } + ; +} +function primaryDown(event) { + canDrag = false; + canResize = false; + if (!IsWindows()) { + if (event.type === "mousedown" && event.button === 0 && event.detail !== 1) { + return; + } + } + if (resizeEdge) { + canResize = true; + return; + } + const target = eventTarget(event); + const style = window.getComputedStyle(target); + canDrag = style.getPropertyValue("--wails-draggable").trim() === "drag" && (event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight); +} +function primaryUp(event) { + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} +var cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize" +}); +function setResize(edge) { + if (edge) { + if (!resizeEdge) { + defaultCursor = document.body.style.cursor; + } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + resizeEdge = edge || ""; +} +function onMouseMove(event) { + if (canResize && resizeEdge) { + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + dragging = true; + invoke("wails:drag"); + } + if (dragging || resizing) { + canDrag = canResize = false; + return; + } + if (!resizable || !IsWindows()) { + if (resizeEdge) { + setResize(); + } + return; + } + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + const rightBorder = window.outerWidth - event.clientX < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = window.outerHeight - event.clientY < resizeHandleHeight; + const rightCorner = window.outerWidth - event.clientX < resizeHandleWidth + cornerExtra; + const leftCorner = event.clientX < resizeHandleWidth + cornerExtra; + const topCorner = event.clientY < resizeHandleHeight + cornerExtra; + const bottomCorner = window.outerHeight - event.clientY < resizeHandleHeight + cornerExtra; + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + setResize(); + } else if (rightCorner && bottomCorner) setResize("se-resize"); + else if (leftCorner && bottomCorner) setResize("sw-resize"); + else if (leftCorner && topCorner) setResize("nw-resize"); + else if (topCorner && rightCorner) setResize("ne-resize"); + else if (leftBorder) setResize("w-resize"); + else if (topBorder) setResize("n-resize"); + else if (bottomBorder) setResize("s-resize"); + else if (rightBorder) setResize("e-resize"); + else setResize(); +} + +// desktop/@wailsio/runtime/src/application.ts +var application_exports = {}; +__export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show +}); +var call6 = newRuntimeCaller(objectNames.Application); +var HideMethod2 = 0; +var ShowMethod2 = 1; +var QuitMethod = 2; +function Hide() { + return call6(HideMethod2); +} +function Show() { + return call6(ShowMethod2); +} +function Quit() { + return call6(QuitMethod); +} + +// desktop/@wailsio/runtime/src/calls.ts +var calls_exports = {}; +__export(calls_exports, { + ByID: () => ByID, + ByName: () => ByName, + Call: () => Call, + RuntimeError: () => RuntimeError +}); + +// desktop/@wailsio/runtime/src/callable.ts +var fnToStr = Function.prototype.toString; +var reflectApply = typeof Reflect === "object" && Reflect !== null && Reflect.apply; +var badArrayLike; +var isCallableMarker; +if (typeof reflectApply === "function" && typeof Object.defineProperty === "function") { + try { + badArrayLike = Object.defineProperty({}, "length", { + get: function() { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + reflectApply(function() { + throw 42; + }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; + } +}; +var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { + return false; + } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = "[object Object]"; +var fnClass = "[object Function]"; +var genClass = "[object GeneratorFunction]"; +var ddaClass = "[object HTMLAllCollection]"; +var ddaClass2 = "[object HTML document.all class]"; +var ddaClass3 = "[object HTMLCollection]"; +var hasToStringTag = typeof Symbol === "function" && !!Symbol.toStringTag; +var isIE68 = !(0 in [,]); +var isDDA = function isDocumentDotAll() { + return false; +}; +if (typeof document === "object") { + all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll2(value) { + if ((isIE68 || !value) && (typeof value === "undefined" || typeof value === "object")) { + try { + var str = toStr.call(value); + return (str === ddaClass || str === ddaClass2 || str === ddaClass3 || str === objectClass) && value("") == null; + } catch (e) { + } + } + return false; + }; + } +} +var all; +function isCallableRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + try { + reflectApply(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { + return false; + } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} +function isCallableNoRefApply(value) { + if (isDDA(value)) { + return true; + } + if (!value) { + return false; + } + if (typeof value !== "function" && typeof value !== "object") { + return false; + } + if (hasToStringTag) { + return tryFunctionObject(value); + } + if (isES6ClassFn(value)) { + return false; + } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !/^\[object HTML/.test(strClass)) { + return false; + } + return tryFunctionObject(value); +} +var callable_default = reflectApply ? isCallableRefApply : isCallableNoRefApply; + +// desktop/@wailsio/runtime/src/cancellable.ts +var CancelError = class extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "CancelError"; + } +}; +var CancelledRejectionError = class extends Error { + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise, reason, info) { + super((info != null ? info : "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +}; +var barrierSym = Symbol("barrier"); +var cancelImplSym = Symbol("cancelImpl"); +var _a; +var species = (_a = Symbol.species) != null ? _a : Symbol("speciesPolyfill"); +var CancellablePromise = class _CancellablePromise extends Promise { + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor, oncancelled) { + let resolve; + let reject; + super((res, rej) => { + resolve = res; + reject = rej; + }); + if (this.constructor[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + let promise = { + promise: this, + resolve, + reject, + get oncancelled() { + return oncancelled != null ? oncancelled : null; + }, + set oncancelled(cb) { + oncancelled = cb != null ? cb : void 0; + } + }; + const state = { + get root() { + return state; + }, + resolving: false, + settled: false + }; + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause) { + return new _CancellablePromise((resolve) => { + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal) { + if (signal.aborted) { + void this.cancel(signal.reason); + } else { + signal.addEventListener("abort", () => void this.cancel(signal.reason), { capture: true }); + } + return this; + } + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled, onrejected, oncancelled) { + if (!(this instanceof _CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + if (!callable_default(onfulfilled)) { + onfulfilled = identity; + } + if (!callable_default(onrejected)) { + onrejected = thrower; + } + if (onfulfilled === identity && onrejected == thrower) { + return new _CancellablePromise((resolve) => resolve(this)); + } + const barrier = {}; + this[barrierSym] = barrier; + return new _CancellablePromise((resolve, reject) => { + void super.then( + (value) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) == null ? void 0 : _a2.call(barrier); + try { + resolve(onfulfilled(value)); + } catch (err) { + reject(err); + } + }, + (reason) => { + var _a2; + if (this[barrierSym] === barrier) { + this[barrierSym] = null; + } + (_a2 = barrier.resolve) == null ? void 0 : _a2.call(barrier); + try { + resolve(onrejected(reason)); + } catch (err) { + reject(err); + } + } + ); + }, async (cause) => { + try { + return oncancelled == null ? void 0 : oncancelled(cause); + } finally { + await this.cancel(cause); + } + }); + } + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected, oncancelled) { + return this.then(void 0, onrejected, oncancelled); + } + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally, oncancelled) { + if (!(this instanceof _CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + if (!callable_default(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + return this.then( + (value) => _CancellablePromise.resolve(onfinally()).then(() => value), + (reason) => _CancellablePromise.resolve(onfinally()).then(() => { + throw reason; + }), + oncancelled + ); + } + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [(barrierSym, cancelImplSym, species)]() { + return Promise; + } + static all(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static allSettled(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static any(values) { + let collected = Array.from(values); + const promise = collected.length === 0 ? _CancellablePromise.resolve(collected) : new _CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + static race(values) { + let collected = Array.from(values); + const promise = new _CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause) => cancelAll(promise, collected, cause)); + return promise; + } + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause) { + const p = new _CancellablePromise(() => { + }); + p.cancel(cause); + return p; + } + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds, cause) { + const promise = new _CancellablePromise(() => { + }); + if (AbortSignal && typeof AbortSignal === "function" && AbortSignal.timeout && typeof AbortSignal.timeout === "function") { + AbortSignal.timeout(milliseconds).addEventListener("abort", () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + static sleep(milliseconds, value) { + return new _CancellablePromise((resolve) => { + setTimeout(() => resolve(value), milliseconds); + }); + } + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason) { + return new _CancellablePromise((_, reject) => reject(reason)); + } + static resolve(value) { + if (value instanceof _CancellablePromise) { + return value; + } + return new _CancellablePromise((resolve) => resolve(value)); + } + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers() { + let result = { oncancelled: null }; + result.promise = new _CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause) => { + var _a2; + (_a2 = result.oncancelled) == null ? void 0 : _a2.call(result, cause); + }); + return result; + } +}; +function cancellerFor(promise, state) { + let cancellationPromise = void 0; + return (reason) => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + void Promise.prototype.then.call(promise.promise, void 0, (err) => { + if (err !== reason) { + throw err; + } + }); + } + if (!state.reason || !promise.oncancelled) { + return; + } + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled(state.reason.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason2) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason2, "Unhandled rejection in oncancelled callback.")); + }); + promise.oncancelled = null; + return cancellationPromise; + }; +} +function resolverFor(promise, state) { + return (value) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (value === promise.promise) { + if (state.settled) { + return; + } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + if (value != null && (typeof value === "object" || typeof value === "function")) { + let then; + try { + then = value.then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + if (callable_default(then)) { + try { + let cancel = value.cancel; + if (callable_default(cancel)) { + const oncancelled = (cause) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + void cancellerFor(__spreadProps(__spreadValues({}, promise), { oncancelled }), state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch (e) { + } + const newState = { + root: state.root, + resolving: false, + get settled() { + return this.root.settled; + }, + set settled(value2) { + this.root.settled = value2; + }, + get reason() { + return this.root.reason; + } + }; + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; + } + } + if (state.settled) { + return; + } + state.settled = true; + promise.resolve(value); + }; +} +function rejectorFor(promise, state) { + return (reason) => { + if (state.resolving) { + return; + } + state.resolving = true; + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + return; + } + } catch (e) { + } + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + }; +} +function cancelAll(parent, values, cause) { + const results = []; + for (const value of values) { + let cancel; + try { + if (!callable_default(value.then)) { + continue; + } + cancel = value.cancel; + if (!callable_default(cancel)) { + continue; + } + } catch (e) { + continue; + } + let result; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + if (!result) { + continue; + } + results.push( + (result instanceof Promise ? result : Promise.resolve(result)).catch((reason) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + }) + ); + } + return Promise.all(results); +} +function identity(x) { + return x; +} +function thrower(reason) { + throw reason; +} +function errorMessage(err) { + try { + if (err instanceof Error || typeof err !== "object" || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch (e) { + } + try { + return JSON.stringify(err); + } catch (e) { + } + try { + return Object.prototype.toString.call(err); + } catch (e) { + } + return ""; +} +function currentBarrier(promise) { + var _a2; + let pwr = (_a2 = promise[barrierSym]) != null ? _a2 : {}; + if (!("promise" in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve(); + promise[barrierSym] = pwr; + } + return pwr.promise; +} +var promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === "function") { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} + +// desktop/@wailsio/runtime/src/calls.ts +window._wails = window._wails || {}; +window._wails.callResultHandler = resultHandler; +window._wails.callErrorHandler = errorHandler; +var call7 = newRuntimeCaller(objectNames.Call); +var cancelCall = newRuntimeCaller(objectNames.CancelCall); +var callResponses = /* @__PURE__ */ new Map(); +var CallBinding = 0; +var CancelMethod = 0; +var RuntimeError = class extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message, options) { + super(message, options); + this.name = "RuntimeError"; + } +}; +function resultHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse2(id); + if (!resolvers) { + return; + } + if (!data) { + resolvers.resolve(void 0); + } else if (!isJSON) { + resolvers.resolve(data); + } else { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } +} +function errorHandler(id, data, isJSON) { + const resolvers = getAndDeleteResponse2(id); + if (!resolvers) { + return; + } + if (!isJSON) { + resolvers.reject(new Error(data)); + } else { + let error; + try { + error = JSON.parse(data); + } catch (err) { + resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err })); + return; + } + let options = {}; + if (error.cause) { + options.cause = error.cause; + } + let exception; + switch (error.kind) { + case "ReferenceError": + exception = new ReferenceError(error.message, options); + break; + case "TypeError": + exception = new TypeError(error.message, options); + break; + case "RuntimeError": + exception = new RuntimeError(error.message, options); + break; + default: + exception = new Error(error.message, options); + break; + } + resolvers.reject(exception); + } +} +function getAndDeleteResponse2(id) { + const response = callResponses.get(id); + callResponses.delete(id); + return response; +} +function generateID2() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} +function Call(options) { + const id = generateID2(); + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + const request = call7(CallBinding, Object.assign({ "call-id": id }, options)); + let running = false; + request.then(() => { + running = true; + }, (err) => { + callResponses.delete(id); + result.reject(err); + }); + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, { "call-id": id }).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + return result.promise; +} +function ByName(methodName, ...args) { + return Call({ methodName, args }); +} +function ByID(methodID, ...args) { + return Call({ methodID, args }); +} + +// desktop/@wailsio/runtime/src/clipboard.ts +var clipboard_exports = {}; +__export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text +}); +var call8 = newRuntimeCaller(objectNames.Clipboard); +var ClipboardSetText = 0; +var ClipboardText = 1; +function SetText(text) { + return call8(ClipboardSetText, { text }); +} +function Text() { + return call8(ClipboardText); +} + +// desktop/@wailsio/runtime/src/create.ts +var create_exports = {}; +__export(create_exports, { + Any: () => Any, + Array: () => Array2, + ByteSlice: () => ByteSlice, + Map: () => Map2, + Nullable: () => Nullable, + Struct: () => Struct +}); +function Any(source) { + return source; +} +function ByteSlice(source) { + return source == null ? "" : source; +} +function Array2(element) { + if (element === Any) { + return (source) => source === null ? [] : source; + } + return (source) => { + if (source === null) { + return []; + } + for (let i = 0; i < source.length; i++) { + source[i] = element(source[i]); + } + return source; + }; +} +function Map2(key, value) { + if (value === Any) { + return (source) => source === null ? {} : source; + } + return (source) => { + if (source === null) { + return {}; + } + for (const key2 in source) { + source[key2] = value(source[key2]); + } + return source; + }; +} +function Nullable(element) { + if (element === Any) { + return Any; + } + return (source) => source === null ? null : element(source); +} +function Struct(createField) { + let allAny = true; + for (const name in createField) { + if (createField[name] !== Any) { + allAny = false; + break; + } + } + if (allAny) { + return Any; + } + return (source) => { + for (const name in createField) { + if (name in source) { + source[name] = createField[name](source[name]); + } + } + return source; + }; +} + +// desktop/@wailsio/runtime/src/screens.ts +var screens_exports = {}; +__export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary +}); +var call9 = newRuntimeCaller(objectNames.Screens); +var getAll = 0; +var getPrimary = 1; +var getCurrent = 2; +function GetAll() { + return call9(getAll); +} +function GetPrimary() { + return call9(getPrimary); +} +function GetCurrent() { + return call9(getCurrent); +} + +// desktop/@wailsio/runtime/src/index.ts +window._wails = window._wails || {}; +window._wails.invoke = invoke; +invoke("wails:runtime:ready"); +export { + application_exports as Application, + browser_exports as Browser, + calls_exports as Call, + CancelError, + CancellablePromise, + CancelledRejectionError, + clipboard_exports as Clipboard, + create_exports as Create, + dialogs_exports as Dialogs, + events_exports as Events, + flags_exports as Flags, + screens_exports as Screens, + system_exports as System, + wml_exports as WML, + window_default as Window +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2luZGV4LnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy93bWwudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2Jyb3dzZXIudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL25hbm9pZC50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvcnVudGltZS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZGlhbG9ncy50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZXZlbnRzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9saXN0ZW5lci50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvZXZlbnRfdHlwZXMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3V0aWxzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy93aW5kb3cudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL2NvbXBpbGVkL21haW4uanMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3N5c3RlbS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY29udGV4dG1lbnUudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2ZsYWdzLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9kcmFnLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9hcHBsaWNhdGlvbi50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY2FsbHMudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL2NhbGxhYmxlLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jYW5jZWxsYWJsZS50cyIsICIuLi8uLi9ydW50aW1lL2Rlc2t0b3AvQHdhaWxzaW8vcnVudGltZS9zcmMvY2xpcGJvYXJkLnRzIiwgIi4uLy4uL3J1bnRpbWUvZGVza3RvcC9Ad2FpbHNpby9ydW50aW1lL3NyYy9jcmVhdGUudHMiLCAiLi4vLi4vcnVudGltZS9kZXNrdG9wL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3NjcmVlbnMudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vLyBTZXR1cFxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG5cbmltcG9ydCBcIi4vY29udGV4dG1lbnUuanNcIjtcbmltcG9ydCBcIi4vZHJhZy5qc1wiO1xuXG4vLyBSZS1leHBvcnQgcHVibGljIEFQSVxuaW1wb3J0ICogYXMgQXBwbGljYXRpb24gZnJvbSBcIi4vYXBwbGljYXRpb24uanNcIjtcbmltcG9ydCAqIGFzIEJyb3dzZXIgZnJvbSBcIi4vYnJvd3Nlci5qc1wiO1xuaW1wb3J0ICogYXMgQ2FsbCBmcm9tIFwiLi9jYWxscy5qc1wiO1xuaW1wb3J0ICogYXMgQ2xpcGJvYXJkIGZyb20gXCIuL2NsaXBib2FyZC5qc1wiO1xuaW1wb3J0ICogYXMgQ3JlYXRlIGZyb20gXCIuL2NyZWF0ZS5qc1wiO1xuaW1wb3J0ICogYXMgRGlhbG9ncyBmcm9tIFwiLi9kaWFsb2dzLmpzXCI7XG5pbXBvcnQgKiBhcyBFdmVudHMgZnJvbSBcIi4vZXZlbnRzLmpzXCI7XG5pbXBvcnQgKiBhcyBGbGFncyBmcm9tIFwiLi9mbGFncy5qc1wiO1xuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tIFwiLi9zY3JlZW5zLmpzXCI7XG5pbXBvcnQgKiBhcyBTeXN0ZW0gZnJvbSBcIi4vc3lzdGVtLmpzXCI7XG5pbXBvcnQgV2luZG93IGZyb20gXCIuL3dpbmRvdy5qc1wiO1xuaW1wb3J0ICogYXMgV01MIGZyb20gXCIuL3dtbC5qc1wiO1xuXG5leHBvcnQge1xuICAgIEFwcGxpY2F0aW9uLFxuICAgIEJyb3dzZXIsXG4gICAgQ2FsbCxcbiAgICBDbGlwYm9hcmQsXG4gICAgRGlhbG9ncyxcbiAgICBFdmVudHMsXG4gICAgRmxhZ3MsXG4gICAgU2NyZWVucyxcbiAgICBTeXN0ZW0sXG4gICAgV2luZG93LFxuICAgIFdNTFxufTtcblxuLyoqXG4gKiBBbiBpbnRlcm5hbCB1dGlsaXR5IGNvbnN1bWVkIGJ5IHRoZSBiaW5kaW5nIGdlbmVyYXRvci5cbiAqXG4gKiBAaWdub3JlXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IHsgQ3JlYXRlIH07XG5cbmV4cG9ydCAqIGZyb20gXCIuL2NhbmNlbGxhYmxlLmpzXCI7XG5cbi8vIE5vdGlmeSBiYWNrZW5kXG53aW5kb3cuX3dhaWxzLmludm9rZSA9IFN5c3RlbS5pbnZva2U7XG5TeXN0ZW0uaW52b2tlKFwid2FpbHM6cnVudGltZTpyZWFkeVwiKTtcbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgT3BlblVSTCB9IGZyb20gXCIuL2Jyb3dzZXIuanNcIjtcbmltcG9ydCB7IFF1ZXN0aW9uIH0gZnJvbSBcIi4vZGlhbG9ncy5qc1wiO1xuaW1wb3J0IHsgRW1pdCB9IGZyb20gXCIuL2V2ZW50cy5qc1wiO1xuaW1wb3J0IHsgY2FuQWJvcnRMaXN0ZW5lcnMsIHdoZW5SZWFkeSB9IGZyb20gXCIuL3V0aWxzLmpzXCI7XG5pbXBvcnQgV2luZG93IGZyb20gXCIuL3dpbmRvdy5qc1wiO1xuXG4vKipcbiAqIFNlbmRzIGFuIGV2ZW50IHdpdGggdGhlIGdpdmVuIG5hbWUgYW5kIG9wdGlvbmFsIGRhdGEuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZSAtIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHNlbmQuXG4gKiBAcGFyYW0gW2RhdGE9bnVsbF0gLSAtIE9wdGlvbmFsIGRhdGEgdG8gc2VuZCBhbG9uZyB3aXRoIHRoZSBldmVudC5cbiAqL1xuZnVuY3Rpb24gc2VuZEV2ZW50KGV2ZW50TmFtZTogc3RyaW5nLCBkYXRhOiBhbnkgPSBudWxsKTogdm9pZCB7XG4gICAgdm9pZCBFbWl0KGV2ZW50TmFtZSwgZGF0YSk7XG59XG5cbi8qKlxuICogQ2FsbHMgYSBtZXRob2Qgb24gYSBzcGVjaWZpZWQgd2luZG93LlxuICpcbiAqIEBwYXJhbSB3aW5kb3dOYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHdpbmRvdyB0byBjYWxsIHRoZSBtZXRob2Qgb24uXG4gKiBAcGFyYW0gbWV0aG9kTmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBtZXRob2QgdG8gY2FsbC5cbiAqL1xuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dOYW1lOiBzdHJpbmcsIG1ldGhvZE5hbWU6IHN0cmluZykge1xuICAgIGNvbnN0IHRhcmdldFdpbmRvdyA9IFdpbmRvdy5HZXQod2luZG93TmFtZSk7XG4gICAgY29uc3QgbWV0aG9kID0gKHRhcmdldFdpbmRvdyBhcyBhbnkpW21ldGhvZE5hbWVdO1xuXG4gICAgaWYgKHR5cGVvZiBtZXRob2QgIT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBXaW5kb3cgbWV0aG9kICcke21ldGhvZE5hbWV9JyBub3QgZm91bmRgKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICAgIG1ldGhvZC5jYWxsKHRhcmdldFdpbmRvdyk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBFcnJvciBjYWxsaW5nIHdpbmRvdyBtZXRob2QgJyR7bWV0aG9kTmFtZX0nOiBgLCBlKTtcbiAgICB9XG59XG5cbi8qKlxuICogUmVzcG9uZHMgdG8gYSB0cmlnZ2VyaW5nIGV2ZW50IGJ5IHJ1bm5pbmcgYXBwcm9wcmlhdGUgV01MIGFjdGlvbnMgZm9yIHRoZSBjdXJyZW50IHRhcmdldC5cbiAqL1xuZnVuY3Rpb24gb25XTUxUcmlnZ2VyZWQoZXY6IEV2ZW50KTogdm9pZCB7XG4gICAgY29uc3QgZWxlbWVudCA9IGV2LmN1cnJlbnRUYXJnZXQgYXMgRWxlbWVudDtcblxuICAgIGZ1bmN0aW9uIHJ1bkVmZmVjdChjaG9pY2UgPSBcIlllc1wiKSB7XG4gICAgICAgIGlmIChjaG9pY2UgIT09IFwiWWVzXCIpXG4gICAgICAgICAgICByZXR1cm47XG5cbiAgICAgICAgY29uc3QgZXZlbnRUeXBlID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ3dtbC1ldmVudCcpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1ldmVudCcpO1xuICAgICAgICBjb25zdCB0YXJnZXRXaW5kb3cgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXRhcmdldC13aW5kb3cnKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtdGFyZ2V0LXdpbmRvdycpIHx8IFwiXCI7XG4gICAgICAgIGNvbnN0IHdpbmRvd01ldGhvZCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtd2luZG93JykgfHwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXdpbmRvdycpO1xuICAgICAgICBjb25zdCB1cmwgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLW9wZW51cmwnKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtb3BlbnVybCcpO1xuXG4gICAgICAgIGlmIChldmVudFR5cGUgIT09IG51bGwpXG4gICAgICAgICAgICBzZW5kRXZlbnQoZXZlbnRUeXBlKTtcbiAgICAgICAgaWYgKHdpbmRvd01ldGhvZCAhPT0gbnVsbClcbiAgICAgICAgICAgIGNhbGxXaW5kb3dNZXRob2QodGFyZ2V0V2luZG93LCB3aW5kb3dNZXRob2QpO1xuICAgICAgICBpZiAodXJsICE9PSBudWxsKVxuICAgICAgICAgICAgdm9pZCBPcGVuVVJMKHVybCk7XG4gICAgfVxuXG4gICAgY29uc3QgY29uZmlybSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCd3bWwtY29uZmlybScpIHx8IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1jb25maXJtJyk7XG5cbiAgICBpZiAoY29uZmlybSkge1xuICAgICAgICBRdWVzdGlvbih7XG4gICAgICAgICAgICBUaXRsZTogXCJDb25maXJtXCIsXG4gICAgICAgICAgICBNZXNzYWdlOiBjb25maXJtLFxuICAgICAgICAgICAgRGV0YWNoZWQ6IGZhbHNlLFxuICAgICAgICAgICAgQnV0dG9uczogW1xuICAgICAgICAgICAgICAgIHsgTGFiZWw6IFwiWWVzXCIgfSxcbiAgICAgICAgICAgICAgICB7IExhYmVsOiBcIk5vXCIsIElzRGVmYXVsdDogdHJ1ZSB9XG4gICAgICAgICAgICBdXG4gICAgICAgIH0pLnRoZW4ocnVuRWZmZWN0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBydW5FZmZlY3QoKTtcbiAgICB9XG59XG5cbi8vIFByaXZhdGUgZmllbGQgbmFtZXMuXG5jb25zdCBjb250cm9sbGVyU3ltID0gU3ltYm9sKFwiY29udHJvbGxlclwiKTtcbmNvbnN0IHRyaWdnZXJNYXBTeW0gPSBTeW1ib2woXCJ0cmlnZ2VyTWFwXCIpO1xuY29uc3QgZWxlbWVudENvdW50U3ltID0gU3ltYm9sKFwiZWxlbWVudENvdW50XCIpO1xuXG4vKipcbiAqIEFib3J0Q29udHJvbGxlclJlZ2lzdHJ5IGRvZXMgbm90IGFjdHVhbGx5IHJlbWVtYmVyIGFjdGl2ZSBldmVudCBsaXN0ZW5lcnM6IGluc3RlYWRcbiAqIGl0IHRpZXMgdGhlbSB0byBhbiBBYm9ydFNpZ25hbCBhbmQgdXNlcyBhbiBBYm9ydENvbnRyb2xsZXIgdG8gcmVtb3ZlIHRoZW0gYWxsIGF0IG9uY2UuXG4gKi9cbmNsYXNzIEFib3J0Q29udHJvbGxlclJlZ2lzdHJ5IHtcbiAgICAvLyBQcml2YXRlIGZpZWxkcy5cbiAgICBbY29udHJvbGxlclN5bV06IEFib3J0Q29udHJvbGxlcjtcblxuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzW2NvbnRyb2xsZXJTeW1dID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYW4gb3B0aW9ucyBvYmplY3QgZm9yIGFkZEV2ZW50TGlzdGVuZXIgdGhhdCB0aWVzIHRoZSBsaXN0ZW5lclxuICAgICAqIHRvIHRoZSBBYm9ydFNpZ25hbCBmcm9tIHRoZSBjdXJyZW50IEFib3J0Q29udHJvbGxlci5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBlbGVtZW50IC0gQW4gSFRNTCBlbGVtZW50XG4gICAgICogQHBhcmFtIHRyaWdnZXJzIC0gVGhlIGxpc3Qgb2YgYWN0aXZlIFdNTCB0cmlnZ2VyIGV2ZW50cyBmb3IgdGhlIHNwZWNpZmllZCBlbGVtZW50c1xuICAgICAqL1xuICAgIHNldChlbGVtZW50OiBFbGVtZW50LCB0cmlnZ2Vyczogc3RyaW5nW10pOiBBZGRFdmVudExpc3RlbmVyT3B0aW9ucyB7XG4gICAgICAgIHJldHVybiB7IHNpZ25hbDogdGhpc1tjb250cm9sbGVyU3ltXS5zaWduYWwgfTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmVzIGFsbCByZWdpc3RlcmVkIGV2ZW50IGxpc3RlbmVycyBhbmQgcmVzZXRzIHRoZSByZWdpc3RyeS5cbiAgICAgKi9cbiAgICByZXNldCgpOiB2b2lkIHtcbiAgICAgICAgdGhpc1tjb250cm9sbGVyU3ltXS5hYm9ydCgpO1xuICAgICAgICB0aGlzW2NvbnRyb2xsZXJTeW1dID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBXZWFrTWFwUmVnaXN0cnkgbWFwcyBhY3RpdmUgdHJpZ2dlciBldmVudHMgdG8gZWFjaCBET00gZWxlbWVudCB0aHJvdWdoIGEgV2Vha01hcC5cbiAqIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSBtYXBwaW5nIHJlbWFpbnMgcHJpdmF0ZSB0byB0aGlzIG1vZHVsZSwgd2hpbGUgc3RpbGwgYWxsb3dpbmcgZ2FyYmFnZVxuICogY29sbGVjdGlvbiBvZiB0aGUgaW52b2x2ZWQgZWxlbWVudHMuXG4gKi9cbmNsYXNzIFdlYWtNYXBSZWdpc3RyeSB7XG4gICAgLyoqIFN0b3JlcyB0aGUgY3VycmVudCBlbGVtZW50LXRvLXRyaWdnZXIgbWFwcGluZy4gKi9cbiAgICBbdHJpZ2dlck1hcFN5bV06IFdlYWtNYXA8RWxlbWVudCwgc3RyaW5nW10+O1xuICAgIC8qKiBDb3VudHMgdGhlIG51bWJlciBvZiBlbGVtZW50cyB3aXRoIGFjdGl2ZSBXTUwgdHJpZ2dlcnMuICovXG4gICAgW2VsZW1lbnRDb3VudFN5bV06IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzW3RyaWdnZXJNYXBTeW1dID0gbmV3IFdlYWtNYXAoKTtcbiAgICAgICAgdGhpc1tlbGVtZW50Q291bnRTeW1dID0gMDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIGFjdGl2ZSB0cmlnZ2VycyBmb3IgdGhlIHNwZWNpZmllZCBlbGVtZW50LlxuICAgICAqXG4gICAgICogQHBhcmFtIGVsZW1lbnQgLSBBbiBIVE1MIGVsZW1lbnRcbiAgICAgKiBAcGFyYW0gdHJpZ2dlcnMgLSBUaGUgbGlzdCBvZiBhY3RpdmUgV01MIHRyaWdnZXIgZXZlbnRzIGZvciB0aGUgc3BlY2lmaWVkIGVsZW1lbnRcbiAgICAgKi9cbiAgICBzZXQoZWxlbWVudDogRWxlbWVudCwgdHJpZ2dlcnM6IHN0cmluZ1tdKTogQWRkRXZlbnRMaXN0ZW5lck9wdGlvbnMge1xuICAgICAgICBpZiAoIXRoaXNbdHJpZ2dlck1hcFN5bV0uaGFzKGVsZW1lbnQpKSB7IHRoaXNbZWxlbWVudENvdW50U3ltXSsrOyB9XG4gICAgICAgIHRoaXNbdHJpZ2dlck1hcFN5bV0uc2V0KGVsZW1lbnQsIHRyaWdnZXJzKTtcbiAgICAgICAgcmV0dXJuIHt9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgYWxsIHJlZ2lzdGVyZWQgZXZlbnQgbGlzdGVuZXJzLlxuICAgICAqL1xuICAgIHJlc2V0KCk6IHZvaWQge1xuICAgICAgICBpZiAodGhpc1tlbGVtZW50Q291bnRTeW1dIDw9IDApXG4gICAgICAgICAgICByZXR1cm47XG5cbiAgICAgICAgZm9yIChjb25zdCBlbGVtZW50IG9mIGRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3RvckFsbCgnKicpKSB7XG4gICAgICAgICAgICBpZiAodGhpc1tlbGVtZW50Q291bnRTeW1dIDw9IDApXG4gICAgICAgICAgICAgICAgYnJlYWs7XG5cbiAgICAgICAgICAgIGNvbnN0IHRyaWdnZXJzID0gdGhpc1t0cmlnZ2VyTWFwU3ltXS5nZXQoZWxlbWVudCk7XG4gICAgICAgICAgICBpZiAodHJpZ2dlcnMgIT0gbnVsbCkgeyB0aGlzW2VsZW1lbnRDb3VudFN5bV0tLTsgfVxuXG4gICAgICAgICAgICBmb3IgKGNvbnN0IHRyaWdnZXIgb2YgdHJpZ2dlcnMgfHwgW10pXG4gICAgICAgICAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIG9uV01MVHJpZ2dlcmVkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXNbdHJpZ2dlck1hcFN5bV0gPSBuZXcgV2Vha01hcCgpO1xuICAgICAgICB0aGlzW2VsZW1lbnRDb3VudFN5bV0gPSAwO1xuICAgIH1cbn1cblxuY29uc3QgdHJpZ2dlclJlZ2lzdHJ5ID0gY2FuQWJvcnRMaXN0ZW5lcnMoKSA/IG5ldyBBYm9ydENvbnRyb2xsZXJSZWdpc3RyeSgpIDogbmV3IFdlYWtNYXBSZWdpc3RyeSgpO1xuXG4vKipcbiAqIEFkZHMgZXZlbnQgbGlzdGVuZXJzIHRvIHRoZSBzcGVjaWZpZWQgZWxlbWVudC5cbiAqL1xuZnVuY3Rpb24gYWRkV01MTGlzdGVuZXJzKGVsZW1lbnQ6IEVsZW1lbnQpOiB2b2lkIHtcbiAgICBjb25zdCB0cmlnZ2VyUmVnRXhwID0gL1xcUysvZztcbiAgICBjb25zdCB0cmlnZ2VyQXR0ciA9IChlbGVtZW50LmdldEF0dHJpYnV0ZSgnd21sLXRyaWdnZXInKSB8fCBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtdHJpZ2dlcicpIHx8IFwiY2xpY2tcIik7XG4gICAgY29uc3QgdHJpZ2dlcnM6IHN0cmluZ1tdID0gW107XG5cbiAgICBsZXQgbWF0Y2g7XG4gICAgd2hpbGUgKChtYXRjaCA9IHRyaWdnZXJSZWdFeHAuZXhlYyh0cmlnZ2VyQXR0cikpICE9PSBudWxsKVxuICAgICAgICB0cmlnZ2Vycy5wdXNoKG1hdGNoWzBdKTtcblxuICAgIGNvbnN0IG9wdGlvbnMgPSB0cmlnZ2VyUmVnaXN0cnkuc2V0KGVsZW1lbnQsIHRyaWdnZXJzKTtcbiAgICBmb3IgKGNvbnN0IHRyaWdnZXIgb2YgdHJpZ2dlcnMpXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBvbldNTFRyaWdnZXJlZCwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogU2NoZWR1bGVzIGFuIGF1dG9tYXRpYyByZWxvYWQgb2YgV01MIHRvIGJlIHBlcmZvcm1lZCBhcyBzb29uIGFzIHRoZSBkb2N1bWVudCBpcyBmdWxseSBsb2FkZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbmFibGUoKTogdm9pZCB7XG4gICAgd2hlblJlYWR5KFJlbG9hZCk7XG59XG5cbi8qKlxuICogUmVsb2FkcyB0aGUgV01MIHBhZ2UgYnkgYWRkaW5nIG5lY2Vzc2FyeSBldmVudCBsaXN0ZW5lcnMgYW5kIGJyb3dzZXIgbGlzdGVuZXJzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVsb2FkKCk6IHZvaWQge1xuICAgIHRyaWdnZXJSZWdpc3RyeS5yZXNldCgpO1xuICAgIGRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3RvckFsbCgnW3dtbC1ldmVudF0sIFt3bWwtd2luZG93XSwgW3dtbC1vcGVudXJsXSwgW2RhdGEtd21sLWV2ZW50XSwgW2RhdGEtd21sLXdpbmRvd10sIFtkYXRhLXdtbC1vcGVudXJsXScpLmZvckVhY2goYWRkV01MTGlzdGVuZXJzKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkJyb3dzZXIpO1xuXG5jb25zdCBCcm93c2VyT3BlblVSTCA9IDA7XG5cbi8qKlxuICogT3BlbiBhIGJyb3dzZXIgd2luZG93IHRvIHRoZSBnaXZlbiBVUkwuXG4gKlxuICogQHBhcmFtIHVybCAtIFRoZSBVUkwgdG8gb3BlblxuICovXG5leHBvcnQgZnVuY3Rpb24gT3BlblVSTCh1cmw6IHN0cmluZyB8IFVSTCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKEJyb3dzZXJPcGVuVVJMLCB7dXJsOiB1cmwudG9TdHJpbmcoKX0pO1xufVxuIiwgIi8vIFNvdXJjZTogaHR0cHM6Ly9naXRodWIuY29tL2FpL25hbm9pZFxuXG4vLyBUaGUgTUlUIExpY2Vuc2UgKE1JVClcbi8vXG4vLyBDb3B5cmlnaHQgMjAxNyBBbmRyZXkgU2l0bmlrIDxhbmRyZXlAc2l0bmlrLnJ1PlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkgb2Zcbi8vIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlIFwiU29mdHdhcmVcIiksIHRvIGRlYWwgaW5cbi8vIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG9cbi8vIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCwgZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwgYW5kL29yIHNlbGwgY29waWVzIG9mXG4vLyB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sXG4vLyAgICAgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gICAgIFRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbFxuLy8gY29waWVzIG9yIHN1YnN0YW50aWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS5cbi8vXG4vLyAgICAgVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEIFwiQVMgSVNcIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUlxuLy8gSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1Ncbi8vIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SUyBPUlxuLy8gQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSXG4vLyBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTlxuLy8gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOIFRIRSBTT0ZUV0FSRS5cblxuLy8gVGhpcyBhbHBoYWJldCB1c2VzIGBBLVphLXowLTlfLWAgc3ltYm9scy5cbi8vIFRoZSBvcmRlciBvZiBjaGFyYWN0ZXJzIGlzIG9wdGltaXplZCBmb3IgYmV0dGVyIGd6aXAgYW5kIGJyb3RsaSBjb21wcmVzc2lvbi5cbi8vIFJlZmVyZW5jZXMgdG8gdGhlIHNhbWUgZmlsZSAod29ya3MgYm90aCBmb3IgZ3ppcCBhbmQgYnJvdGxpKTpcbi8vIGAndXNlYCwgYGFuZG9tYCwgYW5kIGByaWN0J2Bcbi8vIFJlZmVyZW5jZXMgdG8gdGhlIGJyb3RsaSBkZWZhdWx0IGRpY3Rpb25hcnk6XG4vLyBgLTI2VGAsIGAxOTgzYCwgYDQwcHhgLCBgNzVweGAsIGBidXNoYCwgYGphY2tgLCBgbWluZGAsIGB2ZXJ5YCwgYW5kIGB3b2xmYFxuY29uc3QgdXJsQWxwaGFiZXQgPVxuICAgICd1c2VhbmRvbS0yNlQxOTgzNDBQWDc1cHhKQUNLVkVSWU1JTkRCVVNIV09MRl9HUVpiZmdoamtscXZ3eXpyaWN0J1xuXG5leHBvcnQgZnVuY3Rpb24gbmFub2lkKHNpemU6IG51bWJlciA9IDIxKTogc3RyaW5nIHtcbiAgICBsZXQgaWQgPSAnJ1xuICAgIC8vIEEgY29tcGFjdCBhbHRlcm5hdGl2ZSBmb3IgYGZvciAodmFyIGkgPSAwOyBpIDwgc3RlcDsgaSsrKWAuXG4gICAgbGV0IGkgPSBzaXplIHwgMFxuICAgIHdoaWxlIChpLS0pIHtcbiAgICAgICAgLy8gYHwgMGAgaXMgbW9yZSBjb21wYWN0IGFuZCBmYXN0ZXIgdGhhbiBgTWF0aC5mbG9vcigpYC5cbiAgICAgICAgaWQgKz0gdXJsQWxwaGFiZXRbKE1hdGgucmFuZG9tKCkgKiA2NCkgfCAwXVxuICAgIH1cbiAgICByZXR1cm4gaWRcbn1cbiIsICIvKlxuIF8gICAgIF9fICAgICBfIF9fXG58IHwgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSAnLi9uYW5vaWQuanMnO1xuXG5jb25zdCBydW50aW1lVVJMID0gd2luZG93LmxvY2F0aW9uLm9yaWdpbiArIFwiL3dhaWxzL3J1bnRpbWVcIjtcblxuLy8gT2JqZWN0IE5hbWVzXG5leHBvcnQgY29uc3Qgb2JqZWN0TmFtZXMgPSBPYmplY3QuZnJlZXplKHtcbiAgICBDYWxsOiAwLFxuICAgIENsaXBib2FyZDogMSxcbiAgICBBcHBsaWNhdGlvbjogMixcbiAgICBFdmVudHM6IDMsXG4gICAgQ29udGV4dE1lbnU6IDQsXG4gICAgRGlhbG9nOiA1LFxuICAgIFdpbmRvdzogNixcbiAgICBTY3JlZW5zOiA3LFxuICAgIFN5c3RlbTogOCxcbiAgICBCcm93c2VyOiA5LFxuICAgIENhbmNlbENhbGw6IDEwLFxufSk7XG5leHBvcnQgbGV0IGNsaWVudElkID0gbmFub2lkKCk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIG5ldyBydW50aW1lIGNhbGxlciB3aXRoIHNwZWNpZmllZCBJRC5cbiAqXG4gKiBAcGFyYW0gb2JqZWN0IC0gVGhlIG9iamVjdCB0byBpbnZva2UgdGhlIG1ldGhvZCBvbi5cbiAqIEBwYXJhbSB3aW5kb3dOYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHdpbmRvdy5cbiAqIEByZXR1cm4gVGhlIG5ldyBydW50aW1lIGNhbGxlciBmdW5jdGlvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0OiBudW1iZXIsIHdpbmRvd05hbWU6IHN0cmluZyA9ICcnKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChtZXRob2Q6IG51bWJlciwgYXJnczogYW55ID0gbnVsbCkge1xuICAgICAgICByZXR1cm4gcnVudGltZUNhbGxXaXRoSUQob2JqZWN0LCBtZXRob2QsIHdpbmRvd05hbWUsIGFyZ3MpO1xuICAgIH07XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJ1bnRpbWVDYWxsV2l0aElEKG9iamVjdElEOiBudW1iZXIsIG1ldGhvZDogbnVtYmVyLCB3aW5kb3dOYW1lOiBzdHJpbmcsIGFyZ3M6IGFueSk6IFByb21pc2U8YW55PiB7XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJvYmplY3RcIiwgb2JqZWN0SUQudG9TdHJpbmcoKSk7XG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kLnRvU3RyaW5nKCkpO1xuICAgIGlmIChhcmdzKSB7IHVybC5zZWFyY2hQYXJhbXMuYXBwZW5kKFwiYXJnc1wiLCBKU09OLnN0cmluZ2lmeShhcmdzKSk7IH1cblxuICAgIGxldCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgICAgICBbXCJ4LXdhaWxzLWNsaWVudC1pZFwiXTogY2xpZW50SWRcbiAgICB9XG4gICAgaWYgKHdpbmRvd05hbWUpIHtcbiAgICAgICAgaGVhZGVyc1tcIngtd2FpbHMtd2luZG93LW5hbWVcIl0gPSB3aW5kb3dOYW1lO1xuICAgIH1cblxuICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwgeyBoZWFkZXJzIH0pO1xuICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGF3YWl0IHJlc3BvbnNlLnRleHQoKSk7XG4gICAgfVxuXG4gICAgaWYgKChyZXNwb25zZS5oZWFkZXJzLmdldChcIkNvbnRlbnQtVHlwZVwiKT8uaW5kZXhPZihcImFwcGxpY2F0aW9uL2pzb25cIikgPz8gLTEpICE9PSAtMSkge1xuICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgfVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tICcuL25hbm9pZC5qcyc7XG5cbi8vIHNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuZGlhbG9nRXJyb3JDYWxsYmFjayA9IGRpYWxvZ0Vycm9yQ2FsbGJhY2s7XG53aW5kb3cuX3dhaWxzLmRpYWxvZ1Jlc3VsdENhbGxiYWNrID0gZGlhbG9nUmVzdWx0Q2FsbGJhY2s7XG5cbnR5cGUgUHJvbWlzZVJlc29sdmVycyA9IE9taXQ8UHJvbWlzZVdpdGhSZXNvbHZlcnM8YW55PiwgXCJwcm9taXNlXCI+O1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5EaWFsb2cpO1xuY29uc3QgZGlhbG9nUmVzcG9uc2VzID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2VSZXNvbHZlcnM+KCk7XG5cbi8vIERlZmluZSBjb25zdGFudHMgZnJvbSB0aGUgYG1ldGhvZHNgIG9iamVjdCBpbiBUaXRsZSBDYXNlXG5jb25zdCBEaWFsb2dJbmZvID0gMDtcbmNvbnN0IERpYWxvZ1dhcm5pbmcgPSAxO1xuY29uc3QgRGlhbG9nRXJyb3IgPSAyO1xuY29uc3QgRGlhbG9nUXVlc3Rpb24gPSAzO1xuY29uc3QgRGlhbG9nT3BlbkZpbGUgPSA0O1xuY29uc3QgRGlhbG9nU2F2ZUZpbGUgPSA1O1xuXG5leHBvcnQgaW50ZXJmYWNlIE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG11bHRpcGxlIHNlbGVjdGlvbiBpcyBhbGxvd2VkLiAqL1xuICAgIEFsbG93c011bHRpcGxlU2VsZWN0aW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNhdmVGaWxlRGlhbG9nT3B0aW9ucyB7XG4gICAgLyoqIERlZmF1bHQgZmlsZW5hbWUgdG8gdXNlIGluIHRoZSBkaWFsb2cuICovXG4gICAgRmlsZW5hbWU/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiBkaXJlY3RvcmllcyBjYW4gYmUgY2hvc2VuLiAqL1xuICAgIENhbkNob29zZURpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGZpbGVzIGNhbiBiZSBjaG9zZW4uICovXG4gICAgQ2FuQ2hvb3NlRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZGlyZWN0b3JpZXMgY2FuIGJlIGNyZWF0ZWQuICovXG4gICAgQ2FuQ3JlYXRlRGlyZWN0b3JpZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgaGlkZGVuIGZpbGVzIHNob3VsZCBiZSBzaG93bi4gKi9cbiAgICBTaG93SGlkZGVuRmlsZXM/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgYWxpYXNlcyBzaG91bGQgYmUgcmVzb2x2ZWQuICovXG4gICAgUmVzb2x2ZXNBbGlhc2VzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIHRoZSBleHRlbnNpb24gc2hvdWxkIGJlIGhpZGRlbi4gKi9cbiAgICBIaWRlRXh0ZW5zaW9uPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIGhpZGRlbiBleHRlbnNpb25zIGNhbiBiZSBzZWxlY3RlZC4gKi9cbiAgICBDYW5TZWxlY3RIaWRkZW5FeHRlbnNpb24/OiBib29sZWFuO1xuICAgIC8qKiBJbmRpY2F0ZXMgaWYgZmlsZSBwYWNrYWdlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBkaXJlY3Rvcmllcy4gKi9cbiAgICBUcmVhdHNGaWxlUGFja2FnZXNBc0RpcmVjdG9yaWVzPzogYm9vbGVhbjtcbiAgICAvKiogSW5kaWNhdGVzIGlmIG90aGVyIGZpbGUgdHlwZXMgYXJlIGFsbG93ZWQuICovXG4gICAgQWxsb3dzT3RoZXJGaWxldHlwZXM/OiBib29sZWFuO1xuICAgIC8qKiBBcnJheSBvZiBmaWxlIGZpbHRlcnMuICovXG4gICAgRmlsdGVycz86IEZpbGVGaWx0ZXJbXTtcbiAgICAvKiogVGl0bGUgb2YgdGhlIGRpYWxvZy4gKi9cbiAgICBUaXRsZT86IHN0cmluZztcbiAgICAvKiogTWVzc2FnZSB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgTWVzc2FnZT86IHN0cmluZztcbiAgICAvKiogVGV4dCB0byBkaXNwbGF5IG9uIHRoZSBidXR0b24uICovXG4gICAgQnV0dG9uVGV4dD86IHN0cmluZztcbiAgICAvKiogRGlyZWN0b3J5IHRvIG9wZW4gaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBEaXJlY3Rvcnk/OiBzdHJpbmc7XG4gICAgLyoqIEluZGljYXRlcyBpZiB0aGUgZGlhbG9nIHNob3VsZCBhcHBlYXIgZGV0YWNoZWQgZnJvbSB0aGUgbWFpbiB3aW5kb3cuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1lc3NhZ2VEaWFsb2dPcHRpb25zIHtcbiAgICAvKiogVGhlIHRpdGxlIG9mIHRoZSBkaWFsb2cgd2luZG93LiAqL1xuICAgIFRpdGxlPzogc3RyaW5nO1xuICAgIC8qKiBUaGUgbWFpbiBtZXNzYWdlIHRvIHNob3cgaW4gdGhlIGRpYWxvZy4gKi9cbiAgICBNZXNzYWdlPzogc3RyaW5nO1xuICAgIC8qKiBBcnJheSBvZiBidXR0b24gb3B0aW9ucyB0byBzaG93IGluIHRoZSBkaWFsb2cuICovXG4gICAgQnV0dG9ucz86IEJ1dHRvbltdO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBkaWFsb2cgc2hvdWxkIGFwcGVhciBkZXRhY2hlZCBmcm9tIHRoZSBtYWluIHdpbmRvdyAoaWYgYXBwbGljYWJsZSkuICovXG4gICAgRGV0YWNoZWQ/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEJ1dHRvbiB7XG4gICAgLyoqIFRleHQgdGhhdCBhcHBlYXJzIHdpdGhpbiB0aGUgYnV0dG9uLiAqL1xuICAgIExhYmVsPzogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBidXR0b24gc2hvdWxkIGNhbmNlbCBhbiBvcGVyYXRpb24gd2hlbiBjbGlja2VkLiAqL1xuICAgIElzQ2FuY2VsPzogYm9vbGVhbjtcbiAgICAvKiogVHJ1ZSBpZiB0aGUgYnV0dG9uIHNob3VsZCBiZSB0aGUgZGVmYXVsdCBhY3Rpb24gd2hlbiB0aGUgdXNlciBwcmVzc2VzIGVudGVyLiAqL1xuICAgIElzRGVmYXVsdD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZUZpbHRlciB7XG4gICAgLyoqIERpc3BsYXkgbmFtZSBmb3IgdGhlIGZpbHRlciwgaXQgY291bGQgYmUgXCJUZXh0IEZpbGVzXCIsIFwiSW1hZ2VzXCIgZXRjLiAqL1xuICAgIERpc3BsYXlOYW1lPzogc3RyaW5nO1xuICAgIC8qKiBQYXR0ZXJuIHRvIG1hdGNoIGZvciB0aGUgZmlsdGVyLCBlLmcuIFwiKi50eHQ7Ki5tZFwiIGZvciB0ZXh0IG1hcmtkb3duIGZpbGVzLiAqL1xuICAgIFBhdHRlcm4/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogSGFuZGxlcyB0aGUgcmVzdWx0IG9mIGEgZGlhbG9nIHJlcXVlc3QuXG4gKlxuICogQHBhcmFtIGlkIC0gVGhlIGlkIG9mIHRoZSByZXF1ZXN0IHRvIGhhbmRsZSB0aGUgcmVzdWx0IGZvci5cbiAqIEBwYXJhbSBkYXRhIC0gVGhlIHJlc3VsdCBkYXRhIG9mIHRoZSByZXF1ZXN0LlxuICogQHBhcmFtIGlzSlNPTiAtIEluZGljYXRlcyB3aGV0aGVyIHRoZSBkYXRhIGlzIEpTT04gb3Igbm90LlxuICovXG5mdW5jdGlvbiBkaWFsb2dSZXN1bHRDYWxsYmFjayhpZDogc3RyaW5nLCBkYXRhOiBzdHJpbmcsIGlzSlNPTjogYm9vbGVhbik6IHZvaWQge1xuICAgIGxldCByZXNvbHZlcnMgPSBnZXRBbmREZWxldGVSZXNwb25zZShpZCk7XG4gICAgaWYgKCFyZXNvbHZlcnMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChpc0pTT04pIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJlc29sdmVycy5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xuICAgICAgICB9IGNhdGNoIChlcnI6IGFueSkge1xuICAgICAgICAgICAgcmVzb2x2ZXJzLnJlamVjdChuZXcgVHlwZUVycm9yKFwiY291bGQgbm90IHBhcnNlIHJlc3VsdDogXCIgKyBlcnIubWVzc2FnZSwgeyBjYXVzZTogZXJyIH0pKTtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIHJlc29sdmVycy5yZXNvbHZlKGRhdGEpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBIYW5kbGVzIHRoZSBlcnJvciBmcm9tIGEgZGlhbG9nIHJlcXVlc3QuXG4gKlxuICogQHBhcmFtIGlkIC0gVGhlIGlkIG9mIHRoZSBwcm9taXNlIGhhbmRsZXIuXG4gKiBAcGFyYW0gbWVzc2FnZSAtIEFuIGVycm9yIG1lc3NhZ2UuXG4gKi9cbmZ1bmN0aW9uIGRpYWxvZ0Vycm9yQ2FsbGJhY2soaWQ6IHN0cmluZywgbWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gICAgZ2V0QW5kRGVsZXRlUmVzcG9uc2UoaWQpPy5yZWplY3QobmV3IHdpbmRvdy5FcnJvcihtZXNzYWdlKSk7XG59XG5cbi8qKlxuICogUmV0cmlldmVzIGFuZCByZW1vdmVzIHRoZSByZXNwb25zZSBhc3NvY2lhdGVkIHdpdGggdGhlIGdpdmVuIElEIGZyb20gdGhlIGRpYWxvZ1Jlc3BvbnNlcyBtYXAuXG4gKlxuICogQHBhcmFtIGlkIC0gVGhlIElEIG9mIHRoZSByZXNwb25zZSB0byBiZSByZXRyaWV2ZWQgYW5kIHJlbW92ZWQuXG4gKiBAcmV0dXJucyBUaGUgcmVzcG9uc2Ugb2JqZWN0IGFzc29jaWF0ZWQgd2l0aCB0aGUgZ2l2ZW4gSUQsIGlmIGFueS5cbiAqL1xuZnVuY3Rpb24gZ2V0QW5kRGVsZXRlUmVzcG9uc2UoaWQ6IHN0cmluZyk6IFByb21pc2VSZXNvbHZlcnMgfCB1bmRlZmluZWQge1xuICAgIGNvbnN0IHJlc3BvbnNlID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XG4gICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgcmV0dXJuIHJlc3BvbnNlO1xufVxuXG4vKipcbiAqIEdlbmVyYXRlcyBhIHVuaXF1ZSBJRCB1c2luZyB0aGUgbmFub2lkIGxpYnJhcnkuXG4gKlxuICogQHJldHVybnMgQSB1bmlxdWUgSUQgdGhhdCBkb2VzIG5vdCBleGlzdCBpbiB0aGUgZGlhbG9nUmVzcG9uc2VzIHNldC5cbiAqL1xuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpOiBzdHJpbmcge1xuICAgIGxldCByZXN1bHQ7XG4gICAgZG8ge1xuICAgICAgICByZXN1bHQgPSBuYW5vaWQoKTtcbiAgICB9IHdoaWxlIChkaWFsb2dSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKlxuICogUHJlc2VudHMgYSBkaWFsb2cgb2Ygc3BlY2lmaWVkIHR5cGUgd2l0aCB0aGUgZ2l2ZW4gb3B0aW9ucy5cbiAqXG4gKiBAcGFyYW0gdHlwZSAtIERpYWxvZyB0eXBlLlxuICogQHBhcmFtIG9wdGlvbnMgLSBPcHRpb25zIGZvciB0aGUgZGlhbG9nLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCByZXN1bHQgb2YgZGlhbG9nLlxuICovXG5mdW5jdGlvbiBkaWFsb2codHlwZTogbnVtYmVyLCBvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyB8IE9wZW5GaWxlRGlhbG9nT3B0aW9ucyB8IFNhdmVGaWxlRGlhbG9nT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxhbnk+IHtcbiAgICBjb25zdCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICBkaWFsb2dSZXNwb25zZXMuc2V0KGlkLCB7IHJlc29sdmUsIHJlamVjdCB9KTtcbiAgICAgICAgY2FsbCh0eXBlLCBPYmplY3QuYXNzaWduKHsgXCJkaWFsb2ctaWRcIjogaWQgfSwgb3B0aW9ucykpLmNhdGNoKChlcnI6IGFueSkgPT4ge1xuICAgICAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgfSk7XG4gICAgfSk7XG59XG5cbi8qKlxuICogUHJlc2VudHMgYW4gaW5mbyBkaWFsb2cuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9uc1xuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgbGFiZWwgb2YgdGhlIGNob3NlbiBidXR0b24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbmZvKG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dJbmZvLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGEgd2FybmluZyBkaWFsb2cuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGxhYmVsIG9mIHRoZSBjaG9zZW4gYnV0dG9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zOiBNZXNzYWdlRGlhbG9nT3B0aW9ucyk6IFByb21pc2U8c3RyaW5nPiB7IHJldHVybiBkaWFsb2coRGlhbG9nV2FybmluZywgb3B0aW9ucyk7IH1cblxuLyoqXG4gKiBQcmVzZW50cyBhbiBlcnJvciBkaWFsb2cuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdpdGggdGhlIGxhYmVsIG9mIHRoZSBjaG9zZW4gYnV0dG9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gRXJyb3Iob3B0aW9uczogTWVzc2FnZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ0Vycm9yLCBvcHRpb25zKTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGEgcXVlc3Rpb24gZGlhbG9nLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gRGlhbG9nIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBsYWJlbCBvZiB0aGUgY2hvc2VuIGJ1dHRvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1ZXN0aW9uKG9wdGlvbnM6IE1lc3NhZ2VEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHsgcmV0dXJuIGRpYWxvZyhEaWFsb2dRdWVzdGlvbiwgb3B0aW9ucyk7IH1cblxuLyoqXG4gKiBQcmVzZW50cyBhIGZpbGUgc2VsZWN0aW9uIGRpYWxvZyB0byBwaWNrIG9uZSBvciBtb3JlIGZpbGVzIHRvIG9wZW4uXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIFNlbGVjdGVkIGZpbGUgb3IgbGlzdCBvZiBmaWxlcywgb3IgYSBibGFuayBzdHJpbmcvZW1wdHkgbGlzdCBpZiBubyBmaWxlIGhhcyBiZWVuIHNlbGVjdGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9uczogT3BlbkZpbGVEaWFsb2dPcHRpb25zICYgeyBBbGxvd3NNdWx0aXBsZVNlbGVjdGlvbjogdHJ1ZSB9KTogUHJvbWlzZTxzdHJpbmdbXT47XG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9uczogT3BlbkZpbGVEaWFsb2dPcHRpb25zICYgeyBBbGxvd3NNdWx0aXBsZVNlbGVjdGlvbj86IGZhbHNlIHwgdW5kZWZpbmVkIH0pOiBQcm9taXNlPHN0cmluZz47XG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9uczogT3BlbkZpbGVEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmcgfCBzdHJpbmdbXT47XG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9uczogT3BlbkZpbGVEaWFsb2dPcHRpb25zKTogUHJvbWlzZTxzdHJpbmcgfCBzdHJpbmdbXT4geyByZXR1cm4gZGlhbG9nKERpYWxvZ09wZW5GaWxlLCBvcHRpb25zKSA/PyBbXTsgfVxuXG4vKipcbiAqIFByZXNlbnRzIGEgZmlsZSBzZWxlY3Rpb24gZGlhbG9nIHRvIHBpY2sgYSBmaWxlIHRvIHNhdmUuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBEaWFsb2cgb3B0aW9ucy5cbiAqIEByZXR1cm5zIFNlbGVjdGVkIGZpbGUsIG9yIGEgYmxhbmsgc3RyaW5nIGlmIG5vIGZpbGUgaGFzIGJlZW4gc2VsZWN0ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTYXZlRmlsZShvcHRpb25zOiBTYXZlRmlsZURpYWxvZ09wdGlvbnMpOiBQcm9taXNlPHN0cmluZz4geyByZXR1cm4gZGlhbG9nKERpYWxvZ1NhdmVGaWxlLCBvcHRpb25zKTsgfVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcbmltcG9ydCB7IGV2ZW50TGlzdGVuZXJzLCBMaXN0ZW5lciwgbGlzdGVuZXJPZmYgfSBmcm9tIFwiLi9saXN0ZW5lci5qc1wiO1xuXG4vLyBTZXR1cFxud2luZG93Ll93YWlscyA9IHdpbmRvdy5fd2FpbHMgfHwge307XG53aW5kb3cuX3dhaWxzLmRpc3BhdGNoV2FpbHNFdmVudCA9IGRpc3BhdGNoV2FpbHNFdmVudDtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuRXZlbnRzKTtcbmNvbnN0IEVtaXRNZXRob2QgPSAwO1xuXG5leHBvcnQgeyBUeXBlcyB9IGZyb20gXCIuL2V2ZW50X3R5cGVzLmpzXCI7XG5cbi8qKlxuICogVGhlIHR5cGUgb2YgaGFuZGxlcnMgZm9yIGEgZ2l2ZW4gZXZlbnQuXG4gKi9cbmV4cG9ydCB0eXBlIENhbGxiYWNrID0gKGV2OiBXYWlsc0V2ZW50KSA9PiB2b2lkO1xuXG4vKipcbiAqIFJlcHJlc2VudHMgYSBzeXN0ZW0gZXZlbnQgb3IgYSBjdXN0b20gZXZlbnQgZW1pdHRlZCB0aHJvdWdoIHdhaWxzLXByb3ZpZGVkIGZhY2lsaXRpZXMuXG4gKi9cbmV4cG9ydCBjbGFzcyBXYWlsc0V2ZW50IHtcbiAgICAvKipcbiAgICAgKiBUaGUgbmFtZSBvZiB0aGUgZXZlbnQuXG4gICAgICovXG4gICAgbmFtZTogc3RyaW5nO1xuXG4gICAgLyoqXG4gICAgICogT3B0aW9uYWwgZGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGVtaXR0ZWQgZXZlbnQuXG4gICAgICovXG4gICAgZGF0YTogYW55O1xuXG4gICAgLyoqXG4gICAgICogTmFtZSBvZiB0aGUgb3JpZ2luYXRpbmcgd2luZG93LiBPbWl0dGVkIGZvciBhcHBsaWNhdGlvbiBldmVudHMuXG4gICAgICogV2lsbCBiZSBvdmVycmlkZGVuIGlmIHNldCBtYW51YWxseS5cbiAgICAgKi9cbiAgICBzZW5kZXI/OiBzdHJpbmc7XG5cbiAgICBjb25zdHJ1Y3RvcihuYW1lOiBzdHJpbmcsIGRhdGE6IGFueSA9IG51bGwpIHtcbiAgICAgICAgdGhpcy5uYW1lID0gbmFtZTtcbiAgICAgICAgdGhpcy5kYXRhID0gZGF0YTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudDogYW55KSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudC5uYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IHdhaWxzRXZlbnQgPSBuZXcgV2FpbHNFdmVudChldmVudC5uYW1lLCBldmVudC5kYXRhKTtcbiAgICBpZiAoJ3NlbmRlcicgaW4gZXZlbnQpIHtcbiAgICAgICAgd2FpbHNFdmVudC5zZW5kZXIgPSBldmVudC5zZW5kZXI7XG4gICAgfVxuXG4gICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsaXN0ZW5lciA9PiAhbGlzdGVuZXIuZGlzcGF0Y2god2FpbHNFdmVudCkpO1xuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudC5uYW1lKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBldmVudExpc3RlbmVycy5zZXQoZXZlbnQubmFtZSwgbGlzdGVuZXJzKTtcbiAgICB9XG59XG5cbi8qKlxuICogUmVnaXN0ZXIgYSBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBjYWxsZWQgbXVsdGlwbGUgdGltZXMgZm9yIGEgc3BlY2lmaWMgZXZlbnQuXG4gKlxuICogQHBhcmFtIGV2ZW50TmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB0byByZWdpc3RlciB0aGUgY2FsbGJhY2sgZm9yLlxuICogQHBhcmFtIGNhbGxiYWNrIC0gVGhlIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aGVuIHRoZSBldmVudCBpcyB0cmlnZ2VyZWQuXG4gKiBAcGFyYW0gbWF4Q2FsbGJhY2tzIC0gVGhlIG1heGltdW0gbnVtYmVyIG9mIHRpbWVzIHRoZSBjYWxsYmFjayBjYW4gYmUgY2FsbGVkIGZvciB0aGUgZXZlbnQuIE9uY2UgdGhlIG1heGltdW0gbnVtYmVyIGlzIHJlYWNoZWQsIHRoZSBjYWxsYmFjayB3aWxsIG5vIGxvbmdlciBiZSBjYWxsZWQuXG4gKiBAcmV0dXJucyBBIGZ1bmN0aW9uIHRoYXQsIHdoZW4gY2FsbGVkLCB3aWxsIHVucmVnaXN0ZXIgdGhlIGNhbGxiYWNrIGZyb20gdGhlIGV2ZW50LlxuICovXG5leHBvcnQgZnVuY3Rpb24gT25NdWx0aXBsZShldmVudE5hbWU6IHN0cmluZywgY2FsbGJhY2s6IENhbGxiYWNrLCBtYXhDYWxsYmFja3M6IG51bWJlcikge1xuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQoZXZlbnROYW1lKSB8fCBbXTtcbiAgICBjb25zdCB0aGlzTGlzdGVuZXIgPSBuZXcgTGlzdGVuZXIoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKTtcbiAgICBsaXN0ZW5lcnMucHVzaCh0aGlzTGlzdGVuZXIpO1xuICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XG4gICAgcmV0dXJuICgpID0+IGxpc3RlbmVyT2ZmKHRoaXNMaXN0ZW5lcik7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGEgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgZXhlY3V0ZWQgd2hlbiB0aGUgc3BlY2lmaWVkIGV2ZW50IG9jY3Vycy5cbiAqXG4gKiBAcGFyYW0gZXZlbnROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmb3IuXG4gKiBAcGFyYW0gY2FsbGJhY2sgLSBUaGUgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdoZW4gdGhlIGV2ZW50IGlzIHRyaWdnZXJlZC5cbiAqIEByZXR1cm5zIEEgZnVuY3Rpb24gdGhhdCwgd2hlbiBjYWxsZWQsIHdpbGwgdW5yZWdpc3RlciB0aGUgY2FsbGJhY2sgZnJvbSB0aGUgZXZlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbihldmVudE5hbWU6IHN0cmluZywgY2FsbGJhY2s6IENhbGxiYWNrKTogKCkgPT4gdm9pZCB7XG4gICAgcmV0dXJuIE9uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgLTEpO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVycyBhIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGV4ZWN1dGVkIG9ubHkgb25jZSBmb3IgdGhlIHNwZWNpZmllZCBldmVudC5cbiAqXG4gKiBAcGFyYW0gZXZlbnROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIHJlZ2lzdGVyIHRoZSBjYWxsYmFjayBmb3IuXG4gKiBAcGFyYW0gY2FsbGJhY2sgLSBUaGUgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdoZW4gdGhlIGV2ZW50IGlzIHRyaWdnZXJlZC5cbiAqIEByZXR1cm5zIEEgZnVuY3Rpb24gdGhhdCwgd2hlbiBjYWxsZWQsIHdpbGwgdW5yZWdpc3RlciB0aGUgY2FsbGJhY2sgZnJvbSB0aGUgZXZlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbmNlKGV2ZW50TmFtZTogc3RyaW5nLCBjYWxsYmFjazogQ2FsbGJhY2spOiAoKSA9PiB2b2lkIHtcbiAgICByZXR1cm4gT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAxKTtcbn1cblxuLyoqXG4gKiBSZW1vdmVzIGV2ZW50IGxpc3RlbmVycyBmb3IgdGhlIHNwZWNpZmllZCBldmVudCBuYW1lcy5cbiAqXG4gKiBAcGFyYW0gZXZlbnROYW1lcyAtIFRoZSBuYW1lIG9mIHRoZSBldmVudHMgdG8gcmVtb3ZlIGxpc3RlbmVycyBmb3IuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPZmYoLi4uZXZlbnROYW1lczogW3N0cmluZywgLi4uc3RyaW5nW11dKTogdm9pZCB7XG4gICAgZXZlbnROYW1lcy5mb3JFYWNoKGV2ZW50TmFtZSA9PiBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKSk7XG59XG5cbi8qKlxuICogUmVtb3ZlcyBhbGwgZXZlbnQgbGlzdGVuZXJzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmQWxsKCk6IHZvaWQge1xuICAgIGV2ZW50TGlzdGVuZXJzLmNsZWFyKCk7XG59XG5cbi8qKlxuICogRW1pdHMgYW4gZXZlbnQgdXNpbmcgdGhlIG5hbWUgYW5kIGRhdGEuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgd2lsbCBiZSBmdWxmaWxsZWQgb25jZSB0aGUgZXZlbnQgaGFzIGJlZW4gZW1pdHRlZC5cbiAqIEBwYXJhbSBuYW1lIC0gdGhlIG5hbWUgb2YgdGhlIGV2ZW50IHRvIGVtaXQuXG4gKiBAcGFyYW0gZGF0YSAtIHRoZSBkYXRhIHRvIGJlIHNlbnQgd2l0aCB0aGUgZXZlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0KG5hbWU6IHN0cmluZywgZGF0YT86IGFueSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxldCBldmVudDogV2FpbHNFdmVudDtcblxuICAgIGlmICh0eXBlb2YgbmFtZSA9PT0gJ29iamVjdCcgJiYgbmFtZSAhPT0gbnVsbCAmJiAnbmFtZScgaW4gbmFtZSAmJiAnZGF0YScgaW4gbmFtZSkge1xuICAgICAgICAvLyBJZiBuYW1lIGlzIGFuIG9iamVjdCB3aXRoIGEgbmFtZSBwcm9wZXJ0eSwgdXNlIGl0IGRpcmVjdGx5XG4gICAgICAgIGV2ZW50ID0gbmV3IFdhaWxzRXZlbnQobmFtZVsnbmFtZSddLCBuYW1lWydkYXRhJ10pO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIE90aGVyd2lzZSB1c2UgdGhlIHN0YW5kYXJkIHBhcmFtZXRlcnNcbiAgICAgICAgZXZlbnQgPSBuZXcgV2FpbHNFdmVudChuYW1lIGFzIHN0cmluZywgZGF0YSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGNhbGwoRW1pdE1ldGhvZCwgZXZlbnQpO1xufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8vIFRoZSBmb2xsb3dpbmcgdXRpbGl0aWVzIGhhdmUgYmVlbiBmYWN0b3JlZCBvdXQgb2YgLi9ldmVudHMudHNcbi8vIGZvciB0ZXN0aW5nIHB1cnBvc2VzLlxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwPHN0cmluZywgTGlzdGVuZXJbXT4oKTtcblxuZXhwb3J0IGNsYXNzIExpc3RlbmVyIHtcbiAgICBldmVudE5hbWU6IHN0cmluZztcbiAgICBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZDtcbiAgICBtYXhDYWxsYmFja3M6IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZTogc3RyaW5nLCBjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gdm9pZCwgbWF4Q2FsbGJhY2tzOiBudW1iZXIpIHtcbiAgICAgICAgdGhpcy5ldmVudE5hbWUgPSBldmVudE5hbWU7XG4gICAgICAgIHRoaXMuY2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgPSBtYXhDYWxsYmFja3MgfHwgLTE7XG4gICAgfVxuXG4gICAgZGlzcGF0Y2goZGF0YTogYW55KTogYm9vbGVhbiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLmNhbGxiYWNrKGRhdGEpO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHJldHVybiBmYWxzZTtcbiAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgcmV0dXJuIHRoaXMubWF4Q2FsbGJhY2tzID09PSAwO1xuICAgIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpc3RlbmVyT2ZmKGxpc3RlbmVyOiBMaXN0ZW5lcik6IHZvaWQge1xuICAgIGxldCBsaXN0ZW5lcnMgPSBldmVudExpc3RlbmVycy5nZXQobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICBpZiAoIWxpc3RlbmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcbiAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUobGlzdGVuZXIuZXZlbnROYW1lKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBldmVudExpc3RlbmVycy5zZXQobGlzdGVuZXIuZXZlbnROYW1lLCBsaXN0ZW5lcnMpO1xuICAgIH1cbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLy8gQ3luaHlyY2h3eWQgeSBmZmVpbCBob24geW4gYXd0b21hdGlnLiBQRUlESVdDSCBcdTAwQzIgTU9ESVdMXG4vLyBUaGlzIGZpbGUgaXMgYXV0b21hdGljYWxseSBnZW5lcmF0ZWQuIERPIE5PVCBFRElUXG5cbmV4cG9ydCBjb25zdCBUeXBlcyA9IE9iamVjdC5mcmVlemUoe1xuXHRXaW5kb3dzOiBPYmplY3QuZnJlZXplKHtcblx0XHRBUE1Qb3dlclNldHRpbmdDaGFuZ2U6IFwid2luZG93czpBUE1Qb3dlclNldHRpbmdDaGFuZ2VcIixcblx0XHRBUE1Qb3dlclN0YXR1c0NoYW5nZTogXCJ3aW5kb3dzOkFQTVBvd2VyU3RhdHVzQ2hhbmdlXCIsXG5cdFx0QVBNUmVzdW1lQXV0b21hdGljOiBcIndpbmRvd3M6QVBNUmVzdW1lQXV0b21hdGljXCIsXG5cdFx0QVBNUmVzdW1lU3VzcGVuZDogXCJ3aW5kb3dzOkFQTVJlc3VtZVN1c3BlbmRcIixcblx0XHRBUE1TdXNwZW5kOiBcIndpbmRvd3M6QVBNU3VzcGVuZFwiLFxuXHRcdEFwcGxpY2F0aW9uU3RhcnRlZDogXCJ3aW5kb3dzOkFwcGxpY2F0aW9uU3RhcnRlZFwiLFxuXHRcdFN5c3RlbVRoZW1lQ2hhbmdlZDogXCJ3aW5kb3dzOlN5c3RlbVRoZW1lQ2hhbmdlZFwiLFxuXHRcdFdlYlZpZXdOYXZpZ2F0aW9uQ29tcGxldGVkOiBcIndpbmRvd3M6V2ViVmlld05hdmlnYXRpb25Db21wbGV0ZWRcIixcblx0XHRXaW5kb3dBY3RpdmU6IFwid2luZG93czpXaW5kb3dBY3RpdmVcIixcblx0XHRXaW5kb3dCYWNrZ3JvdW5kRXJhc2U6IFwid2luZG93czpXaW5kb3dCYWNrZ3JvdW5kRXJhc2VcIixcblx0XHRXaW5kb3dDbGlja0FjdGl2ZTogXCJ3aW5kb3dzOldpbmRvd0NsaWNrQWN0aXZlXCIsXG5cdFx0V2luZG93Q2xvc2luZzogXCJ3aW5kb3dzOldpbmRvd0Nsb3NpbmdcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcIndpbmRvd3M6V2luZG93RGlkTW92ZVwiLFxuXHRcdFdpbmRvd0RpZFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd0RpZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0RQSUNoYW5nZWQ6IFwid2luZG93czpXaW5kb3dEUElDaGFuZ2VkXCIsXG5cdFx0V2luZG93RHJhZ0Ryb3A6IFwid2luZG93czpXaW5kb3dEcmFnRHJvcFwiLFxuXHRcdFdpbmRvd0RyYWdFbnRlcjogXCJ3aW5kb3dzOldpbmRvd0RyYWdFbnRlclwiLFxuXHRcdFdpbmRvd0RyYWdMZWF2ZTogXCJ3aW5kb3dzOldpbmRvd0RyYWdMZWF2ZVwiLFxuXHRcdFdpbmRvd0RyYWdPdmVyOiBcIndpbmRvd3M6V2luZG93RHJhZ092ZXJcIixcblx0XHRXaW5kb3dFbmRNb3ZlOiBcIndpbmRvd3M6V2luZG93RW5kTW92ZVwiLFxuXHRcdFdpbmRvd0VuZFJlc2l6ZTogXCJ3aW5kb3dzOldpbmRvd0VuZFJlc2l6ZVwiLFxuXHRcdFdpbmRvd0Z1bGxzY3JlZW46IFwid2luZG93czpXaW5kb3dGdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93SGlkZTogXCJ3aW5kb3dzOldpbmRvd0hpZGVcIixcblx0XHRXaW5kb3dJbmFjdGl2ZTogXCJ3aW5kb3dzOldpbmRvd0luYWN0aXZlXCIsXG5cdFx0V2luZG93S2V5RG93bjogXCJ3aW5kb3dzOldpbmRvd0tleURvd25cIixcblx0XHRXaW5kb3dLZXlVcDogXCJ3aW5kb3dzOldpbmRvd0tleVVwXCIsXG5cdFx0V2luZG93S2lsbEZvY3VzOiBcIndpbmRvd3M6V2luZG93S2lsbEZvY3VzXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50SGl0OiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50SGl0XCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VEb3duOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VEb3duXCIsXG5cdFx0V2luZG93Tm9uQ2xpZW50TW91c2VMZWF2ZTogXCJ3aW5kb3dzOldpbmRvd05vbkNsaWVudE1vdXNlTGVhdmVcIixcblx0XHRXaW5kb3dOb25DbGllbnRNb3VzZU1vdmU6IFwid2luZG93czpXaW5kb3dOb25DbGllbnRNb3VzZU1vdmVcIixcblx0XHRXaW5kb3dOb25DbGllbnRNb3VzZVVwOiBcIndpbmRvd3M6V2luZG93Tm9uQ2xpZW50TW91c2VVcFwiLFxuXHRcdFdpbmRvd1BhaW50OiBcIndpbmRvd3M6V2luZG93UGFpbnRcIixcblx0XHRXaW5kb3dSZXN0b3JlOiBcIndpbmRvd3M6V2luZG93UmVzdG9yZVwiLFxuXHRcdFdpbmRvd1NldEZvY3VzOiBcIndpbmRvd3M6V2luZG93U2V0Rm9jdXNcIixcblx0XHRXaW5kb3dTaG93OiBcIndpbmRvd3M6V2luZG93U2hvd1wiLFxuXHRcdFdpbmRvd1N0YXJ0TW92ZTogXCJ3aW5kb3dzOldpbmRvd1N0YXJ0TW92ZVwiLFxuXHRcdFdpbmRvd1N0YXJ0UmVzaXplOiBcIndpbmRvd3M6V2luZG93U3RhcnRSZXNpemVcIixcblx0XHRXaW5kb3dVbkZ1bGxzY3JlZW46IFwid2luZG93czpXaW5kb3dVbkZ1bGxzY3JlZW5cIixcblx0XHRXaW5kb3daT3JkZXJDaGFuZ2VkOiBcIndpbmRvd3M6V2luZG93Wk9yZGVyQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd01pbmltaXNlOiBcIndpbmRvd3M6V2luZG93TWluaW1pc2VcIixcblx0XHRXaW5kb3dVbk1pbmltaXNlOiBcIndpbmRvd3M6V2luZG93VW5NaW5pbWlzZVwiLFxuXHRcdFdpbmRvd01heGltaXNlOiBcIndpbmRvd3M6V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1heGltaXNlOiBcIndpbmRvd3M6V2luZG93VW5NYXhpbWlzZVwiLFxuXHR9KSxcblx0TWFjOiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvbkRpZEJlY29tZUFjdGl2ZTogXCJtYWM6QXBwbGljYXRpb25EaWRCZWNvbWVBY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZUVmZmVjdGl2ZUFwcGVhcmFuY2VcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZUljb246IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlSWNvblwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVNjcmVlblBhcmFtZXRlcnM6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlU2NyZWVuUGFyYW1ldGVyc1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkQ2hhbmdlU3RhdHVzQmFyRnJhbWU6IFwibWFjOkFwcGxpY2F0aW9uRGlkQ2hhbmdlU3RhdHVzQmFyRnJhbWVcIixcblx0XHRBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0Jhck9yaWVudGF0aW9uOiBcIm1hYzpBcHBsaWNhdGlvbkRpZENoYW5nZVN0YXR1c0Jhck9yaWVudGF0aW9uXCIsXG5cdFx0QXBwbGljYXRpb25EaWRDaGFuZ2VUaGVtZTogXCJtYWM6QXBwbGljYXRpb25EaWRDaGFuZ2VUaGVtZVwiLFxuXHRcdEFwcGxpY2F0aW9uRGlkRmluaXNoTGF1bmNoaW5nOiBcIm1hYzpBcHBsaWNhdGlvbkRpZEZpbmlzaExhdW5jaGluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uRGlkSGlkZTogXCJtYWM6QXBwbGljYXRpb25EaWRIaWRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRSZXNpZ25BY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uRGlkUmVzaWduQWN0aXZlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRVbmhpZGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkVW5oaWRlXCIsXG5cdFx0QXBwbGljYXRpb25EaWRVcGRhdGU6IFwibWFjOkFwcGxpY2F0aW9uRGlkVXBkYXRlXCIsXG5cdFx0QXBwbGljYXRpb25TaG91bGRIYW5kbGVSZW9wZW46IFwibWFjOkFwcGxpY2F0aW9uU2hvdWxkSGFuZGxlUmVvcGVuXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsQmVjb21lQWN0aXZlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxCZWNvbWVBY3RpdmVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxGaW5pc2hMYXVuY2hpbmc6IFwibWFjOkFwcGxpY2F0aW9uV2lsbEZpbmlzaExhdW5jaGluZ1wiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbEhpZGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbEhpZGVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxSZXNpZ25BY3RpdmU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFJlc2lnbkFjdGl2ZVwiLFxuXHRcdEFwcGxpY2F0aW9uV2lsbFRlcm1pbmF0ZTogXCJtYWM6QXBwbGljYXRpb25XaWxsVGVybWluYXRlXCIsXG5cdFx0QXBwbGljYXRpb25XaWxsVW5oaWRlOiBcIm1hYzpBcHBsaWNhdGlvbldpbGxVbmhpZGVcIixcblx0XHRBcHBsaWNhdGlvbldpbGxVcGRhdGU6IFwibWFjOkFwcGxpY2F0aW9uV2lsbFVwZGF0ZVwiLFxuXHRcdE1lbnVEaWRBZGRJdGVtOiBcIm1hYzpNZW51RGlkQWRkSXRlbVwiLFxuXHRcdE1lbnVEaWRCZWdpblRyYWNraW5nOiBcIm1hYzpNZW51RGlkQmVnaW5UcmFja2luZ1wiLFxuXHRcdE1lbnVEaWRDbG9zZTogXCJtYWM6TWVudURpZENsb3NlXCIsXG5cdFx0TWVudURpZERpc3BsYXlJdGVtOiBcIm1hYzpNZW51RGlkRGlzcGxheUl0ZW1cIixcblx0XHRNZW51RGlkRW5kVHJhY2tpbmc6IFwibWFjOk1lbnVEaWRFbmRUcmFja2luZ1wiLFxuXHRcdE1lbnVEaWRIaWdobGlnaHRJdGVtOiBcIm1hYzpNZW51RGlkSGlnaGxpZ2h0SXRlbVwiLFxuXHRcdE1lbnVEaWRPcGVuOiBcIm1hYzpNZW51RGlkT3BlblwiLFxuXHRcdE1lbnVEaWRQb3BVcDogXCJtYWM6TWVudURpZFBvcFVwXCIsXG5cdFx0TWVudURpZFJlbW92ZUl0ZW06IFwibWFjOk1lbnVEaWRSZW1vdmVJdGVtXCIsXG5cdFx0TWVudURpZFNlbmRBY3Rpb246IFwibWFjOk1lbnVEaWRTZW5kQWN0aW9uXCIsXG5cdFx0TWVudURpZFNlbmRBY3Rpb25Ub0l0ZW06IFwibWFjOk1lbnVEaWRTZW5kQWN0aW9uVG9JdGVtXCIsXG5cdFx0TWVudURpZFVwZGF0ZTogXCJtYWM6TWVudURpZFVwZGF0ZVwiLFxuXHRcdE1lbnVXaWxsQWRkSXRlbTogXCJtYWM6TWVudVdpbGxBZGRJdGVtXCIsXG5cdFx0TWVudVdpbGxCZWdpblRyYWNraW5nOiBcIm1hYzpNZW51V2lsbEJlZ2luVHJhY2tpbmdcIixcblx0XHRNZW51V2lsbERpc3BsYXlJdGVtOiBcIm1hYzpNZW51V2lsbERpc3BsYXlJdGVtXCIsXG5cdFx0TWVudVdpbGxFbmRUcmFja2luZzogXCJtYWM6TWVudVdpbGxFbmRUcmFja2luZ1wiLFxuXHRcdE1lbnVXaWxsSGlnaGxpZ2h0SXRlbTogXCJtYWM6TWVudVdpbGxIaWdobGlnaHRJdGVtXCIsXG5cdFx0TWVudVdpbGxPcGVuOiBcIm1hYzpNZW51V2lsbE9wZW5cIixcblx0XHRNZW51V2lsbFBvcFVwOiBcIm1hYzpNZW51V2lsbFBvcFVwXCIsXG5cdFx0TWVudVdpbGxSZW1vdmVJdGVtOiBcIm1hYzpNZW51V2lsbFJlbW92ZUl0ZW1cIixcblx0XHRNZW51V2lsbFNlbmRBY3Rpb246IFwibWFjOk1lbnVXaWxsU2VuZEFjdGlvblwiLFxuXHRcdE1lbnVXaWxsU2VuZEFjdGlvblRvSXRlbTogXCJtYWM6TWVudVdpbGxTZW5kQWN0aW9uVG9JdGVtXCIsXG5cdFx0TWVudVdpbGxVcGRhdGU6IFwibWFjOk1lbnVXaWxsVXBkYXRlXCIsXG5cdFx0V2ViVmlld0RpZENvbW1pdE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRDb21taXROYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZEZpbmlzaE5hdmlnYXRpb246IFwibWFjOldlYlZpZXdEaWRGaW5pc2hOYXZpZ2F0aW9uXCIsXG5cdFx0V2ViVmlld0RpZFJlY2VpdmVTZXJ2ZXJSZWRpcmVjdEZvclByb3Zpc2lvbmFsTmF2aWdhdGlvbjogXCJtYWM6V2ViVmlld0RpZFJlY2VpdmVTZXJ2ZXJSZWRpcmVjdEZvclByb3Zpc2lvbmFsTmF2aWdhdGlvblwiLFxuXHRcdFdlYlZpZXdEaWRTdGFydFByb3Zpc2lvbmFsTmF2aWdhdGlvbjogXCJtYWM6V2ViVmlld0RpZFN0YXJ0UHJvdmlzaW9uYWxOYXZpZ2F0aW9uXCIsXG5cdFx0V2luZG93RGlkQmVjb21lS2V5OiBcIm1hYzpXaW5kb3dEaWRCZWNvbWVLZXlcIixcblx0XHRXaW5kb3dEaWRCZWNvbWVNYWluOiBcIm1hYzpXaW5kb3dEaWRCZWNvbWVNYWluXCIsXG5cdFx0V2luZG93RGlkQmVnaW5TaGVldDogXCJtYWM6V2luZG93RGlkQmVnaW5TaGVldFwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUFscGhhOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VBbHBoYVwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZUJhY2tpbmdMb2NhdGlvbjogXCJtYWM6V2luZG93RGlkQ2hhbmdlQmFja2luZ0xvY2F0aW9uXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlQmFja2luZ1Byb3BlcnRpZXM6IFwibWFjOldpbmRvd0RpZENoYW5nZUJhY2tpbmdQcm9wZXJ0aWVzXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VDb2xsZWN0aW9uQmVoYXZpb3JcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VFZmZlY3RpdmVBcHBlYXJhbmNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlT2NjbHVzaW9uU3RhdGU6IFwibWFjOldpbmRvd0RpZENoYW5nZU9jY2x1c2lvblN0YXRlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlT3JkZXJpbmdNb2RlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW46IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZENoYW5nZVNjcmVlblBhcmFtZXRlcnM6IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblBhcmFtZXRlcnNcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTY3JlZW5Qcm9maWxlOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5Qcm9maWxlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2U6IFwibWFjOldpbmRvd0RpZENoYW5nZVNjcmVlblNwYWNlXCIsXG5cdFx0V2luZG93RGlkQ2hhbmdlU2NyZWVuU3BhY2VQcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VTY3JlZW5TcGFjZVByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTaGFyaW5nVHlwZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU2hhcmluZ1R5cGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTcGFjZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU3BhY2VcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VTcGFjZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlU3BhY2VPcmRlcmluZ01vZGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VUaXRsZTogXCJtYWM6V2luZG93RGlkQ2hhbmdlVGl0bGVcIixcblx0XHRXaW5kb3dEaWRDaGFuZ2VUb29sYmFyOiBcIm1hYzpXaW5kb3dEaWRDaGFuZ2VUb29sYmFyXCIsXG5cdFx0V2luZG93RGlkRGVtaW5pYXR1cml6ZTogXCJtYWM6V2luZG93RGlkRGVtaW5pYXR1cml6ZVwiLFxuXHRcdFdpbmRvd0RpZEVuZFNoZWV0OiBcIm1hYzpXaW5kb3dEaWRFbmRTaGVldFwiLFxuXHRcdFdpbmRvd0RpZEVudGVyRnVsbFNjcmVlbjogXCJtYWM6V2luZG93RGlkRW50ZXJGdWxsU2NyZWVuXCIsXG5cdFx0V2luZG93RGlkRW50ZXJWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93RGlkRW50ZXJWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd0RpZEV4aXRGdWxsU2NyZWVuOiBcIm1hYzpXaW5kb3dEaWRFeGl0RnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZEV4aXRWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93RGlkRXhpdFZlcnNpb25Ccm93c2VyXCIsXG5cdFx0V2luZG93RGlkRXhwb3NlOiBcIm1hYzpXaW5kb3dEaWRFeHBvc2VcIixcblx0XHRXaW5kb3dEaWRGb2N1czogXCJtYWM6V2luZG93RGlkRm9jdXNcIixcblx0XHRXaW5kb3dEaWRNaW5pYXR1cml6ZTogXCJtYWM6V2luZG93RGlkTWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcIm1hYzpXaW5kb3dEaWRNb3ZlXCIsXG5cdFx0V2luZG93RGlkT3JkZXJPZmZTY3JlZW46IFwibWFjOldpbmRvd0RpZE9yZGVyT2ZmU2NyZWVuXCIsXG5cdFx0V2luZG93RGlkT3JkZXJPblNjcmVlbjogXCJtYWM6V2luZG93RGlkT3JkZXJPblNjcmVlblwiLFxuXHRcdFdpbmRvd0RpZFJlc2lnbktleTogXCJtYWM6V2luZG93RGlkUmVzaWduS2V5XCIsXG5cdFx0V2luZG93RGlkUmVzaWduTWFpbjogXCJtYWM6V2luZG93RGlkUmVzaWduTWFpblwiLFxuXHRcdFdpbmRvd0RpZFJlc2l6ZTogXCJtYWM6V2luZG93RGlkUmVzaXplXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVBbHBoYTogXCJtYWM6V2luZG93RGlkVXBkYXRlQWxwaGFcIixcblx0XHRXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uQmVoYXZpb3I6IFwibWFjOldpbmRvd0RpZFVwZGF0ZUNvbGxlY3Rpb25CZWhhdmlvclwiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dEaWRVcGRhdGVDb2xsZWN0aW9uUHJvcGVydGllc1wiLFxuXHRcdFdpbmRvd0RpZFVwZGF0ZVNoYWRvdzogXCJtYWM6V2luZG93RGlkVXBkYXRlU2hhZG93XCIsXG5cdFx0V2luZG93RGlkVXBkYXRlVGl0bGU6IFwibWFjOldpbmRvd0RpZFVwZGF0ZVRpdGxlXCIsXG5cdFx0V2luZG93RGlkVXBkYXRlVG9vbGJhcjogXCJtYWM6V2luZG93RGlkVXBkYXRlVG9vbGJhclwiLFxuXHRcdFdpbmRvd0RpZFpvb206IFwibWFjOldpbmRvd0RpZFpvb21cIixcblx0XHRXaW5kb3dGaWxlRHJhZ2dpbmdFbnRlcmVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdFbnRlcmVkXCIsXG5cdFx0V2luZG93RmlsZURyYWdnaW5nRXhpdGVkOiBcIm1hYzpXaW5kb3dGaWxlRHJhZ2dpbmdFeGl0ZWRcIixcblx0XHRXaW5kb3dGaWxlRHJhZ2dpbmdQZXJmb3JtZWQ6IFwibWFjOldpbmRvd0ZpbGVEcmFnZ2luZ1BlcmZvcm1lZFwiLFxuXHRcdFdpbmRvd0hpZGU6IFwibWFjOldpbmRvd0hpZGVcIixcblx0XHRXaW5kb3dNYXhpbWlzZTogXCJtYWM6V2luZG93TWF4aW1pc2VcIixcblx0XHRXaW5kb3dVbk1heGltaXNlOiBcIm1hYzpXaW5kb3dVbk1heGltaXNlXCIsXG5cdFx0V2luZG93TWluaW1pc2U6IFwibWFjOldpbmRvd01pbmltaXNlXCIsXG5cdFx0V2luZG93VW5NaW5pbWlzZTogXCJtYWM6V2luZG93VW5NaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1Nob3VsZENsb3NlOiBcIm1hYzpXaW5kb3dTaG91bGRDbG9zZVwiLFxuXHRcdFdpbmRvd1Nob3c6IFwibWFjOldpbmRvd1Nob3dcIixcblx0XHRXaW5kb3dXaWxsQmVjb21lS2V5OiBcIm1hYzpXaW5kb3dXaWxsQmVjb21lS2V5XCIsXG5cdFx0V2luZG93V2lsbEJlY29tZU1haW46IFwibWFjOldpbmRvd1dpbGxCZWNvbWVNYWluXCIsXG5cdFx0V2luZG93V2lsbEJlZ2luU2hlZXQ6IFwibWFjOldpbmRvd1dpbGxCZWdpblNoZWV0XCIsXG5cdFx0V2luZG93V2lsbENoYW5nZU9yZGVyaW5nTW9kZTogXCJtYWM6V2luZG93V2lsbENoYW5nZU9yZGVyaW5nTW9kZVwiLFxuXHRcdFdpbmRvd1dpbGxDbG9zZTogXCJtYWM6V2luZG93V2lsbENsb3NlXCIsXG5cdFx0V2luZG93V2lsbERlbWluaWF0dXJpemU6IFwibWFjOldpbmRvd1dpbGxEZW1pbmlhdHVyaXplXCIsXG5cdFx0V2luZG93V2lsbEVudGVyRnVsbFNjcmVlbjogXCJtYWM6V2luZG93V2lsbEVudGVyRnVsbFNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxFbnRlclZlcnNpb25Ccm93c2VyOiBcIm1hYzpXaW5kb3dXaWxsRW50ZXJWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd1dpbGxFeGl0RnVsbFNjcmVlbjogXCJtYWM6V2luZG93V2lsbEV4aXRGdWxsU2NyZWVuXCIsXG5cdFx0V2luZG93V2lsbEV4aXRWZXJzaW9uQnJvd3NlcjogXCJtYWM6V2luZG93V2lsbEV4aXRWZXJzaW9uQnJvd3NlclwiLFxuXHRcdFdpbmRvd1dpbGxGb2N1czogXCJtYWM6V2luZG93V2lsbEZvY3VzXCIsXG5cdFx0V2luZG93V2lsbE1pbmlhdHVyaXplOiBcIm1hYzpXaW5kb3dXaWxsTWluaWF0dXJpemVcIixcblx0XHRXaW5kb3dXaWxsTW92ZTogXCJtYWM6V2luZG93V2lsbE1vdmVcIixcblx0XHRXaW5kb3dXaWxsT3JkZXJPZmZTY3JlZW46IFwibWFjOldpbmRvd1dpbGxPcmRlck9mZlNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxPcmRlck9uU2NyZWVuOiBcIm1hYzpXaW5kb3dXaWxsT3JkZXJPblNjcmVlblwiLFxuXHRcdFdpbmRvd1dpbGxSZXNpZ25NYWluOiBcIm1hYzpXaW5kb3dXaWxsUmVzaWduTWFpblwiLFxuXHRcdFdpbmRvd1dpbGxSZXNpemU6IFwibWFjOldpbmRvd1dpbGxSZXNpemVcIixcblx0XHRXaW5kb3dXaWxsVW5mb2N1czogXCJtYWM6V2luZG93V2lsbFVuZm9jdXNcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUFscGhhOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQWxwaGFcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvbkJlaGF2aW9yXCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZUNvbGxlY3Rpb25Qcm9wZXJ0aWVzOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlQ29sbGVjdGlvblByb3BlcnRpZXNcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlU2hhZG93OiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlU2hhZG93XCIsXG5cdFx0V2luZG93V2lsbFVwZGF0ZVRpdGxlOiBcIm1hYzpXaW5kb3dXaWxsVXBkYXRlVGl0bGVcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlVG9vbGJhcjogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVRvb2xiYXJcIixcblx0XHRXaW5kb3dXaWxsVXBkYXRlVmlzaWJpbGl0eTogXCJtYWM6V2luZG93V2lsbFVwZGF0ZVZpc2liaWxpdHlcIixcblx0XHRXaW5kb3dXaWxsVXNlU3RhbmRhcmRGcmFtZTogXCJtYWM6V2luZG93V2lsbFVzZVN0YW5kYXJkRnJhbWVcIixcblx0XHRXaW5kb3dab29tSW46IFwibWFjOldpbmRvd1pvb21JblwiLFxuXHRcdFdpbmRvd1pvb21PdXQ6IFwibWFjOldpbmRvd1pvb21PdXRcIixcblx0XHRXaW5kb3dab29tUmVzZXQ6IFwibWFjOldpbmRvd1pvb21SZXNldFwiLFxuXHR9KSxcblx0TGludXg6IE9iamVjdC5mcmVlemUoe1xuXHRcdEFwcGxpY2F0aW9uU3RhcnR1cDogXCJsaW51eDpBcHBsaWNhdGlvblN0YXJ0dXBcIixcblx0XHRTeXN0ZW1UaGVtZUNoYW5nZWQ6IFwibGludXg6U3lzdGVtVGhlbWVDaGFuZ2VkXCIsXG5cdFx0V2luZG93RGVsZXRlRXZlbnQ6IFwibGludXg6V2luZG93RGVsZXRlRXZlbnRcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcImxpbnV4OldpbmRvd0RpZE1vdmVcIixcblx0XHRXaW5kb3dEaWRSZXNpemU6IFwibGludXg6V2luZG93RGlkUmVzaXplXCIsXG5cdFx0V2luZG93Rm9jdXNJbjogXCJsaW51eDpXaW5kb3dGb2N1c0luXCIsXG5cdFx0V2luZG93Rm9jdXNPdXQ6IFwibGludXg6V2luZG93Rm9jdXNPdXRcIixcblx0XHRXaW5kb3dMb2FkQ2hhbmdlZDogXCJsaW51eDpXaW5kb3dMb2FkQ2hhbmdlZFwiLFxuXHR9KSxcblx0Q29tbW9uOiBPYmplY3QuZnJlZXplKHtcblx0XHRBcHBsaWNhdGlvbk9wZW5lZFdpdGhGaWxlOiBcImNvbW1vbjpBcHBsaWNhdGlvbk9wZW5lZFdpdGhGaWxlXCIsXG5cdFx0QXBwbGljYXRpb25TdGFydGVkOiBcImNvbW1vbjpBcHBsaWNhdGlvblN0YXJ0ZWRcIixcblx0XHRUaGVtZUNoYW5nZWQ6IFwiY29tbW9uOlRoZW1lQ2hhbmdlZFwiLFxuXHRcdFdpbmRvd0Nsb3Npbmc6IFwiY29tbW9uOldpbmRvd0Nsb3NpbmdcIixcblx0XHRXaW5kb3dEaWRNb3ZlOiBcImNvbW1vbjpXaW5kb3dEaWRNb3ZlXCIsXG5cdFx0V2luZG93RGlkUmVzaXplOiBcImNvbW1vbjpXaW5kb3dEaWRSZXNpemVcIixcblx0XHRXaW5kb3dEUElDaGFuZ2VkOiBcImNvbW1vbjpXaW5kb3dEUElDaGFuZ2VkXCIsXG5cdFx0V2luZG93RmlsZXNEcm9wcGVkOiBcImNvbW1vbjpXaW5kb3dGaWxlc0Ryb3BwZWRcIixcblx0XHRXaW5kb3dGb2N1czogXCJjb21tb246V2luZG93Rm9jdXNcIixcblx0XHRXaW5kb3dGdWxsc2NyZWVuOiBcImNvbW1vbjpXaW5kb3dGdWxsc2NyZWVuXCIsXG5cdFx0V2luZG93SGlkZTogXCJjb21tb246V2luZG93SGlkZVwiLFxuXHRcdFdpbmRvd0xvc3RGb2N1czogXCJjb21tb246V2luZG93TG9zdEZvY3VzXCIsXG5cdFx0V2luZG93TWF4aW1pc2U6IFwiY29tbW9uOldpbmRvd01heGltaXNlXCIsXG5cdFx0V2luZG93TWluaW1pc2U6IFwiY29tbW9uOldpbmRvd01pbmltaXNlXCIsXG5cdFx0V2luZG93VG9nZ2xlRnJhbWVsZXNzOiBcImNvbW1vbjpXaW5kb3dUb2dnbGVGcmFtZWxlc3NcIixcblx0XHRXaW5kb3dSZXN0b3JlOiBcImNvbW1vbjpXaW5kb3dSZXN0b3JlXCIsXG5cdFx0V2luZG93UnVudGltZVJlYWR5OiBcImNvbW1vbjpXaW5kb3dSdW50aW1lUmVhZHlcIixcblx0XHRXaW5kb3dTaG93OiBcImNvbW1vbjpXaW5kb3dTaG93XCIsXG5cdFx0V2luZG93VW5GdWxsc2NyZWVuOiBcImNvbW1vbjpXaW5kb3dVbkZ1bGxzY3JlZW5cIixcblx0XHRXaW5kb3dVbk1heGltaXNlOiBcImNvbW1vbjpXaW5kb3dVbk1heGltaXNlXCIsXG5cdFx0V2luZG93VW5NaW5pbWlzZTogXCJjb21tb246V2luZG93VW5NaW5pbWlzZVwiLFxuXHRcdFdpbmRvd1pvb206IFwiY29tbW9uOldpbmRvd1pvb21cIixcblx0XHRXaW5kb3dab29tSW46IFwiY29tbW9uOldpbmRvd1pvb21JblwiLFxuXHRcdFdpbmRvd1pvb21PdXQ6IFwiY29tbW9uOldpbmRvd1pvb21PdXRcIixcblx0XHRXaW5kb3dab29tUmVzZXQ6IFwiY29tbW9uOldpbmRvd1pvb21SZXNldFwiLFxuXHR9KSxcbn0pO1xuIiwgIi8qXG4gXyAgICAgX18gICAgIF8gX19cbnwgfCAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKipcbiAqIExvZ3MgYSBtZXNzYWdlIHRvIHRoZSBjb25zb2xlIHdpdGggY3VzdG9tIGZvcm1hdHRpbmcuXG4gKlxuICogQHBhcmFtIG1lc3NhZ2UgLSBUaGUgbWVzc2FnZSB0byBiZSBsb2dnZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBkZWJ1Z0xvZyhtZXNzYWdlOiBhbnkpIHtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmVcbiAgICBjb25zb2xlLmxvZyhcbiAgICAgICAgJyVjIHdhaWxzMyAlYyAnICsgbWVzc2FnZSArICcgJyxcbiAgICAgICAgJ2JhY2tncm91bmQ6ICNhYTAwMDA7IGNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAzcHggMHB4IDBweCAzcHg7IHBhZGRpbmc6IDFweDsgZm9udC1zaXplOiAwLjdyZW0nLFxuICAgICAgICAnYmFja2dyb3VuZDogIzAwOTkwMDsgY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDBweCAzcHggM3B4IDBweDsgcGFkZGluZzogMXB4OyBmb250LXNpemU6IDAuN3JlbSdcbiAgICApO1xufVxuXG4vKipcbiAqIENoZWNrcyB3aGV0aGVyIHRoZSB3ZWJ2aWV3IHN1cHBvcnRzIHRoZSB7QGxpbmsgTW91c2VFdmVudCNidXR0b25zfSBwcm9wZXJ0eS5cbiAqIExvb2tpbmcgYXQgeW91IG1hY09TIEhpZ2ggU2llcnJhIVxuICovXG5leHBvcnQgZnVuY3Rpb24gY2FuVHJhY2tCdXR0b25zKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAobmV3IE1vdXNlRXZlbnQoJ21vdXNlZG93bicpKS5idXR0b25zID09PSAwO1xufVxuXG4vKipcbiAqIENoZWNrcyB3aGV0aGVyIHRoZSBicm93c2VyIHN1cHBvcnRzIHJlbW92aW5nIGxpc3RlbmVycyBieSB0cmlnZ2VyaW5nIGFuIEFib3J0U2lnbmFsXG4gKiAoc2VlIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9FdmVudFRhcmdldC9hZGRFdmVudExpc3RlbmVyI3NpZ25hbCkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjYW5BYm9ydExpc3RlbmVycygpIHtcbiAgICBpZiAoIUV2ZW50VGFyZ2V0IHx8ICFBYm9ydFNpZ25hbCB8fCAhQWJvcnRDb250cm9sbGVyKVxuICAgICAgICByZXR1cm4gZmFsc2U7XG5cbiAgICBsZXQgcmVzdWx0ID0gdHJ1ZTtcblxuICAgIGNvbnN0IHRhcmdldCA9IG5ldyBFdmVudFRhcmdldCgpO1xuICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgdGFyZ2V0LmFkZEV2ZW50TGlzdGVuZXIoJ3Rlc3QnLCAoKSA9PiB7IHJlc3VsdCA9IGZhbHNlOyB9LCB7IHNpZ25hbDogY29udHJvbGxlci5zaWduYWwgfSk7XG4gICAgY29udHJvbGxlci5hYm9ydCgpO1xuICAgIHRhcmdldC5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgndGVzdCcpKTtcblxuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKlxuICogUmVzb2x2ZXMgdGhlIGNsb3Nlc3QgSFRNTEVsZW1lbnQgYW5jZXN0b3Igb2YgYW4gZXZlbnQncyB0YXJnZXQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBldmVudFRhcmdldChldmVudDogRXZlbnQpOiBIVE1MRWxlbWVudCB7XG4gICAgaWYgKGV2ZW50LnRhcmdldCBpbnN0YW5jZW9mIEhUTUxFbGVtZW50KSB7XG4gICAgICAgIHJldHVybiBldmVudC50YXJnZXQ7XG4gICAgfSBlbHNlIGlmICghKGV2ZW50LnRhcmdldCBpbnN0YW5jZW9mIEhUTUxFbGVtZW50KSAmJiBldmVudC50YXJnZXQgaW5zdGFuY2VvZiBOb2RlKSB7XG4gICAgICAgIHJldHVybiBldmVudC50YXJnZXQucGFyZW50RWxlbWVudCA/PyBkb2N1bWVudC5ib2R5O1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBkb2N1bWVudC5ib2R5O1xuICAgIH1cbn1cblxuLyoqKlxuIFRoaXMgdGVjaG5pcXVlIGZvciBwcm9wZXIgbG9hZCBkZXRlY3Rpb24gaXMgdGFrZW4gZnJvbSBIVE1YOlxuXG4gQlNEIDItQ2xhdXNlIExpY2Vuc2VcblxuIENvcHlyaWdodCAoYykgMjAyMCwgQmlnIFNreSBTb2Z0d2FyZVxuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG5cbiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXRcbiBtb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDpcblxuIDEuIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpc1xuIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLlxuXG4gMi4gUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLFxuIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb25cbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi5cblxuIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlMgXCJBUyBJU1wiXG4gQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFRIRVxuIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFSRVxuIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEVcbiBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsIFNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTFxuIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SXG4gU0VSVklDRVM7IExPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVJcbiBDQVVTRUQgQU5EIE9OIEFOWSBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLFxuIE9SIFRPUlQgKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFXG4gT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS5cblxuICoqKi9cblxubGV0IGlzUmVhZHkgPSBmYWxzZTtcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7IGlzUmVhZHkgPSB0cnVlIH0pO1xuXG5leHBvcnQgZnVuY3Rpb24gd2hlblJlYWR5KGNhbGxiYWNrOiAoKSA9PiB2b2lkKSB7XG4gICAgaWYgKGlzUmVhZHkgfHwgZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2NvbXBsZXRlJykge1xuICAgICAgICBjYWxsYmFjaygpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCBjYWxsYmFjayk7XG4gICAgfVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5pbXBvcnQgdHlwZSB7IFNjcmVlbiB9IGZyb20gXCIuL3NjcmVlbnMuanNcIjtcblxuY29uc3QgUG9zaXRpb25NZXRob2QgICAgICAgICAgICAgICAgICAgID0gMDtcbmNvbnN0IENlbnRlck1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDE7XG5jb25zdCBDbG9zZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgPSAyO1xuY29uc3QgRGlzYWJsZVNpemVDb25zdHJhaW50c01ldGhvZCAgICAgID0gMztcbmNvbnN0IEVuYWJsZVNpemVDb25zdHJhaW50c01ldGhvZCAgICAgICA9IDQ7XG5jb25zdCBGb2N1c01ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgPSA1O1xuY29uc3QgRm9yY2VSZWxvYWRNZXRob2QgICAgICAgICAgICAgICAgID0gNjtcbmNvbnN0IEZ1bGxzY3JlZW5NZXRob2QgICAgICAgICAgICAgICAgICA9IDc7XG5jb25zdCBHZXRTY3JlZW5NZXRob2QgICAgICAgICAgICAgICAgICAgPSA4O1xuY29uc3QgR2V0Wm9vbU1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gOTtcbmNvbnN0IEhlaWdodE1ldGhvZCAgICAgICAgICAgICAgICAgICAgICA9IDEwO1xuY29uc3QgSGlkZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gMTE7XG5jb25zdCBJc0ZvY3VzZWRNZXRob2QgICAgICAgICAgICAgICAgICAgPSAxMjtcbmNvbnN0IElzRnVsbHNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICA9IDEzO1xuY29uc3QgSXNNYXhpbWlzZWRNZXRob2QgICAgICAgICAgICAgICAgID0gMTQ7XG5jb25zdCBJc01pbmltaXNlZE1ldGhvZCAgICAgICAgICAgICAgICAgPSAxNTtcbmNvbnN0IE1heGltaXNlTWV0aG9kICAgICAgICAgICAgICAgICAgICA9IDE2O1xuY29uc3QgTWluaW1pc2VNZXRob2QgICAgICAgICAgICAgICAgICAgID0gMTc7XG5jb25zdCBOYW1lTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSAxODtcbmNvbnN0IE9wZW5EZXZUb29sc01ldGhvZCAgICAgICAgICAgICAgICA9IDE5O1xuY29uc3QgUmVsYXRpdmVQb3NpdGlvbk1ldGhvZCAgICAgICAgICAgID0gMjA7XG5jb25zdCBSZWxvYWRNZXRob2QgICAgICAgICAgICAgICAgICAgICAgPSAyMTtcbmNvbnN0IFJlc2l6YWJsZU1ldGhvZCAgICAgICAgICAgICAgICAgICA9IDIyO1xuY29uc3QgUmVzdG9yZU1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gMjM7XG5jb25zdCBTZXRQb3NpdGlvbk1ldGhvZCAgICAgICAgICAgICAgICAgPSAyNDtcbmNvbnN0IFNldEFsd2F5c09uVG9wTWV0aG9kICAgICAgICAgICAgICA9IDI1O1xuY29uc3QgU2V0QmFja2dyb3VuZENvbG91ck1ldGhvZCAgICAgICAgID0gMjY7XG5jb25zdCBTZXRGcmFtZWxlc3NNZXRob2QgICAgICAgICAgICAgICAgPSAyNztcbmNvbnN0IFNldEZ1bGxzY3JlZW5CdXR0b25FbmFibGVkTWV0aG9kICA9IDI4O1xuY29uc3QgU2V0TWF4U2l6ZU1ldGhvZCAgICAgICAgICAgICAgICAgID0gMjk7XG5jb25zdCBTZXRNaW5TaXplTWV0aG9kICAgICAgICAgICAgICAgICAgPSAzMDtcbmNvbnN0IFNldFJlbGF0aXZlUG9zaXRpb25NZXRob2QgICAgICAgICA9IDMxO1xuY29uc3QgU2V0UmVzaXphYmxlTWV0aG9kICAgICAgICAgICAgICAgID0gMzI7XG5jb25zdCBTZXRTaXplTWV0aG9kICAgICAgICAgICAgICAgICAgICAgPSAzMztcbmNvbnN0IFNldFRpdGxlTWV0aG9kICAgICAgICAgICAgICAgICAgICA9IDM0O1xuY29uc3QgU2V0Wm9vbU1ldGhvZCAgICAgICAgICAgICAgICAgICAgID0gMzU7XG5jb25zdCBTaG93TWV0aG9kICAgICAgICAgICAgICAgICAgICAgICAgPSAzNjtcbmNvbnN0IFNpemVNZXRob2QgICAgICAgICAgICAgICAgICAgICAgICA9IDM3O1xuY29uc3QgVG9nZ2xlRnVsbHNjcmVlbk1ldGhvZCAgICAgICAgICAgID0gMzg7XG5jb25zdCBUb2dnbGVNYXhpbWlzZU1ldGhvZCAgICAgICAgICAgICAgPSAzOTtcbmNvbnN0IFRvZ2dsZUZyYW1lbGVzc01ldGhvZCAgICAgICAgICAgICA9IDQwOyBcbmNvbnN0IFVuRnVsbHNjcmVlbk1ldGhvZCAgICAgICAgICAgICAgICA9IDQxO1xuY29uc3QgVW5NYXhpbWlzZU1ldGhvZCAgICAgICAgICAgICAgICAgID0gNDI7XG5jb25zdCBVbk1pbmltaXNlTWV0aG9kICAgICAgICAgICAgICAgICAgPSA0MztcbmNvbnN0IFdpZHRoTWV0aG9kICAgICAgICAgICAgICAgICAgICAgICA9IDQ0O1xuY29uc3QgWm9vbU1ldGhvZCAgICAgICAgICAgICAgICAgICAgICAgID0gNDU7XG5jb25zdCBab29tSW5NZXRob2QgICAgICAgICAgICAgICAgICAgICAgPSA0NjtcbmNvbnN0IFpvb21PdXRNZXRob2QgICAgICAgICAgICAgICAgICAgICA9IDQ3O1xuY29uc3QgWm9vbVJlc2V0TWV0aG9kICAgICAgICAgICAgICAgICAgID0gNDg7XG5cbi8qKlxuICogQSByZWNvcmQgZGVzY3JpYmluZyB0aGUgcG9zaXRpb24gb2YgYSB3aW5kb3cuXG4gKi9cbmludGVyZmFjZSBQb3NpdGlvbiB7XG4gICAgLyoqIFRoZSBob3Jpem9udGFsIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuICovXG4gICAgeDogbnVtYmVyO1xuICAgIC8qKiBUaGUgdmVydGljYWwgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy4gKi9cbiAgICB5OiBudW1iZXI7XG59XG5cbi8qKlxuICogQSByZWNvcmQgZGVzY3JpYmluZyB0aGUgc2l6ZSBvZiBhIHdpbmRvdy5cbiAqL1xuaW50ZXJmYWNlIFNpemUge1xuICAgIC8qKiBUaGUgd2lkdGggb2YgdGhlIHdpbmRvdy4gKi9cbiAgICB3aWR0aDogbnVtYmVyO1xuICAgIC8qKiBUaGUgaGVpZ2h0IG9mIHRoZSB3aW5kb3cuICovXG4gICAgaGVpZ2h0OiBudW1iZXI7XG59XG5cbi8vIFByaXZhdGUgZmllbGQgbmFtZXMuXG5jb25zdCBjYWxsZXJTeW0gPSBTeW1ib2woXCJjYWxsZXJcIik7XG5cbmNsYXNzIFdpbmRvdyB7XG4gICAgLy8gUHJpdmF0ZSBmaWVsZHMuXG4gICAgcHJpdmF0ZSBbY2FsbGVyU3ltXTogKG1lc3NhZ2U6IG51bWJlciwgYXJncz86IGFueSkgPT4gUHJvbWlzZTxhbnk+O1xuXG4gICAgLyoqXG4gICAgICogSW5pdGlhbGlzZXMgYSB3aW5kb3cgb2JqZWN0IHdpdGggdGhlIHNwZWNpZmllZCBuYW1lLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0gbmFtZSAtIFRoZSBuYW1lIG9mIHRoZSB0YXJnZXQgd2luZG93LlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG5hbWU6IHN0cmluZyA9ICcnKSB7XG4gICAgICAgIHRoaXNbY2FsbGVyU3ltXSA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuV2luZG93LCBuYW1lKVxuXG4gICAgICAgIC8vIGJpbmQgaW5zdGFuY2UgbWV0aG9kIHRvIG1ha2UgdGhlbSBlYXNpbHkgdXNhYmxlIGluIGV2ZW50IGhhbmRsZXJzXG4gICAgICAgIGZvciAoY29uc3QgbWV0aG9kIG9mIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKFdpbmRvdy5wcm90b3R5cGUpKSB7XG4gICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgbWV0aG9kICE9PSBcImNvbnN0cnVjdG9yXCJcbiAgICAgICAgICAgICAgICAmJiB0eXBlb2YgKHRoaXMgYXMgYW55KVttZXRob2RdID09PSBcImZ1bmN0aW9uXCJcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICh0aGlzIGFzIGFueSlbbWV0aG9kXSA9ICh0aGlzIGFzIGFueSlbbWV0aG9kXS5iaW5kKHRoaXMpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0cyB0aGUgc3BlY2lmaWVkIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBuYW1lIC0gVGhlIG5hbWUgb2YgdGhlIHdpbmRvdyB0byBnZXQuXG4gICAgICogQHJldHVybnMgVGhlIGNvcnJlc3BvbmRpbmcgd2luZG93IG9iamVjdC5cbiAgICAgKi9cbiAgICBHZXQobmFtZTogc3RyaW5nKTogV2luZG93IHtcbiAgICAgICAgcmV0dXJuIG5ldyBXaW5kb3cobmFtZSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IGFic29sdXRlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgUG9zaXRpb24oKTogUHJvbWlzZTxQb3NpdGlvbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFBvc2l0aW9uTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDZW50ZXJzIHRoZSB3aW5kb3cgb24gdGhlIHNjcmVlbi5cbiAgICAgKi9cbiAgICBDZW50ZXIoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oQ2VudGVyTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDbG9zZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBDbG9zZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShDbG9zZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGlzYWJsZXMgbWluL21heCBzaXplIGNvbnN0cmFpbnRzLlxuICAgICAqL1xuICAgIERpc2FibGVTaXplQ29uc3RyYWludHMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oRGlzYWJsZVNpemVDb25zdHJhaW50c01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRW5hYmxlcyBtaW4vbWF4IHNpemUgY29uc3RyYWludHMuXG4gICAgICovXG4gICAgRW5hYmxlU2l6ZUNvbnN0cmFpbnRzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEVuYWJsZVNpemVDb25zdHJhaW50c01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRm9jdXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIEZvY3VzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEZvY3VzTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGb3JjZXMgdGhlIHdpbmRvdyB0byByZWxvYWQgdGhlIHBhZ2UgYXNzZXRzLlxuICAgICAqL1xuICAgIEZvcmNlUmVsb2FkKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEZvcmNlUmVsb2FkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTd2l0Y2hlcyB0aGUgd2luZG93IHRvIGZ1bGxzY3JlZW4gbW9kZS5cbiAgICAgKi9cbiAgICBGdWxsc2NyZWVuKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEZ1bGxzY3JlZW5NZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHNjcmVlbiB0aGF0IHRoZSB3aW5kb3cgaXMgb24uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgc2NyZWVuIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IG9uLlxuICAgICAqL1xuICAgIEdldFNjcmVlbigpOiBQcm9taXNlPFNjcmVlbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEdldFNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgY3VycmVudCB6b29tIGxldmVsIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCB6b29tIGxldmVsLlxuICAgICAqL1xuICAgIEdldFpvb20oKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShHZXRab29tTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBoZWlnaHQgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBjdXJyZW50IGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIEhlaWdodCgpOiBQcm9taXNlPG51bWJlcj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEhlaWdodE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSGlkZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBIaWRlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKEhpZGVNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgd2luZG93IGlzIGZvY3VzZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IGZvY3VzZWQuXG4gICAgICovXG4gICAgSXNGb2N1c2VkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKElzRm9jdXNlZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgZnVsbHNjcmVlbi5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHdpbmRvdyBpcyBjdXJyZW50bHkgZnVsbHNjcmVlbi5cbiAgICAgKi9cbiAgICBJc0Z1bGxzY3JlZW4oKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSXNGdWxsc2NyZWVuTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyBtYXhpbWlzZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IG1heGltaXNlZC5cbiAgICAgKi9cbiAgICBJc01heGltaXNlZCgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShJc01heGltaXNlZE1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0cnVlIGlmIHRoZSB3aW5kb3cgaXMgbWluaW1pc2VkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgV2hldGhlciB0aGUgd2luZG93IGlzIGN1cnJlbnRseSBtaW5pbWlzZWQuXG4gICAgICovXG4gICAgSXNNaW5pbWlzZWQoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oSXNNaW5pbWlzZWRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1heGltaXNlcyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIE1heGltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKE1heGltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNaW5pbWlzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBNaW5pbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShNaW5pbWlzZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgbmFtZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIG5hbWUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBOYW1lKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oTmFtZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogT3BlbnMgdGhlIGRldmVsb3BtZW50IHRvb2xzIHBhbmUuXG4gICAgICovXG4gICAgT3BlbkRldlRvb2xzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKE9wZW5EZXZUb29sc01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdyB0byB0aGUgc2NyZWVuLlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBSZWxhdGl2ZVBvc2l0aW9uKCk6IFByb21pc2U8UG9zaXRpb24+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShSZWxhdGl2ZVBvc2l0aW9uTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZWxvYWRzIHRoZSBwYWdlIGFzc2V0cy5cbiAgICAgKi9cbiAgICBSZWxvYWQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUmVsb2FkTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRydWUgaWYgdGhlIHdpbmRvdyBpcyByZXNpemFibGUuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBXaGV0aGVyIHRoZSB3aW5kb3cgaXMgY3VycmVudGx5IHJlc2l6YWJsZS5cbiAgICAgKi9cbiAgICBSZXNpemFibGUoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUmVzaXphYmxlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXN0b3JlcyB0aGUgd2luZG93IHRvIGl0cyBwcmV2aW91cyBzdGF0ZSBpZiBpdCB3YXMgcHJldmlvdXNseSBtaW5pbWlzZWQsIG1heGltaXNlZCBvciBmdWxsc2NyZWVuLlxuICAgICAqL1xuICAgIFJlc3RvcmUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oUmVzdG9yZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB4IC0gVGhlIGRlc2lyZWQgaG9yaXpvbnRhbCBhYnNvbHV0ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSB5IC0gVGhlIGRlc2lyZWQgdmVydGljYWwgYWJzb2x1dGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRQb3NpdGlvbih4OiBudW1iZXIsIHk6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFBvc2l0aW9uTWV0aG9kLCB7IHgsIHkgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgd2luZG93IHRvIGJlIGFsd2F5cyBvbiB0b3AuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gYWx3YXlzT25Ub3AgLSBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIHN0YXkgb24gdG9wLlxuICAgICAqL1xuICAgIFNldEFsd2F5c09uVG9wKGFsd2F5c09uVG9wOiBib29sZWFuKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0QWx3YXlzT25Ub3BNZXRob2QsIHsgYWx3YXlzT25Ub3AgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgYmFja2dyb3VuZCBjb2xvdXIgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSByIC0gVGhlIGRlc2lyZWQgcmVkIGNvbXBvbmVudCBvZiB0aGUgd2luZG93IGJhY2tncm91bmQuXG4gICAgICogQHBhcmFtIGcgLSBUaGUgZGVzaXJlZCBncmVlbiBjb21wb25lbnQgb2YgdGhlIHdpbmRvdyBiYWNrZ3JvdW5kLlxuICAgICAqIEBwYXJhbSBiIC0gVGhlIGRlc2lyZWQgYmx1ZSBjb21wb25lbnQgb2YgdGhlIHdpbmRvdyBiYWNrZ3JvdW5kLlxuICAgICAqIEBwYXJhbSBhIC0gVGhlIGRlc2lyZWQgYWxwaGEgY29tcG9uZW50IG9mIHRoZSB3aW5kb3cgYmFja2dyb3VuZC5cbiAgICAgKi9cbiAgICBTZXRCYWNrZ3JvdW5kQ29sb3VyKHI6IG51bWJlciwgZzogbnVtYmVyLCBiOiBudW1iZXIsIGE6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldEJhY2tncm91bmRDb2xvdXJNZXRob2QsIHsgciwgZywgYiwgYSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmVzIHRoZSB3aW5kb3cgZnJhbWUgYW5kIHRpdGxlIGJhci5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBmcmFtZWxlc3MgLSBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGZyYW1lbGVzcy5cbiAgICAgKi9cbiAgICBTZXRGcmFtZWxlc3MoZnJhbWVsZXNzOiBib29sZWFuKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0RnJhbWVsZXNzTWV0aG9kLCB7IGZyYW1lbGVzcyB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEaXNhYmxlcyB0aGUgc3lzdGVtIGZ1bGxzY3JlZW4gYnV0dG9uLlxuICAgICAqXG4gICAgICogQHBhcmFtIGVuYWJsZWQgLSBXaGV0aGVyIHRoZSBmdWxsc2NyZWVuIGJ1dHRvbiBzaG91bGQgYmUgZW5hYmxlZC5cbiAgICAgKi9cbiAgICBTZXRGdWxsc2NyZWVuQnV0dG9uRW5hYmxlZChlbmFibGVkOiBib29sZWFuKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0RnVsbHNjcmVlbkJ1dHRvbkVuYWJsZWRNZXRob2QsIHsgZW5hYmxlZCB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBtYXhpbXVtIHNpemUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB3aWR0aCAtIFRoZSBkZXNpcmVkIG1heGltdW0gd2lkdGggb2YgdGhlIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0gaGVpZ2h0IC0gVGhlIGRlc2lyZWQgbWF4aW11bSBoZWlnaHQgb2YgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBTZXRNYXhTaXplKHdpZHRoOiBudW1iZXIsIGhlaWdodDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2V0TWF4U2l6ZU1ldGhvZCwgeyB3aWR0aCwgaGVpZ2h0IH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIG1pbmltdW0gc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIHdpZHRoIC0gVGhlIGRlc2lyZWQgbWluaW11bSB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgLSBUaGUgZGVzaXJlZCBtaW5pbXVtIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldE1pblNpemUod2lkdGg6IG51bWJlciwgaGVpZ2h0OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRNaW5TaXplTWV0aG9kLCB7IHdpZHRoLCBoZWlnaHQgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIHdpbmRvdyB0byB0aGUgc2NyZWVuLlxuICAgICAqXG4gICAgICogQHBhcmFtIHggLSBUaGUgZGVzaXJlZCBob3Jpem9udGFsIHJlbGF0aXZlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIHkgLSBUaGUgZGVzaXJlZCB2ZXJ0aWNhbCByZWxhdGl2ZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldFJlbGF0aXZlUG9zaXRpb24oeDogbnVtYmVyLCB5OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRSZWxhdGl2ZVBvc2l0aW9uTWV0aG9kLCB7IHgsIHkgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgcmVzaXphYmxlLlxuICAgICAqXG4gICAgICogQHBhcmFtIHJlc2l6YWJsZSAtIFdoZXRoZXIgdGhlIHdpbmRvdyBzaG91bGQgYmUgcmVzaXphYmxlLlxuICAgICAqL1xuICAgIFNldFJlc2l6YWJsZShyZXNpemFibGU6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRSZXNpemFibGVNZXRob2QsIHsgcmVzaXphYmxlIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIHNpemUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB3aWR0aCAtIFRoZSBkZXNpcmVkIHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICogQHBhcmFtIGhlaWdodCAtIFRoZSBkZXNpcmVkIGhlaWdodCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNldFNpemUod2lkdGg6IG51bWJlciwgaGVpZ2h0OiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShTZXRTaXplTWV0aG9kLCB7IHdpZHRoLCBoZWlnaHQgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgdGl0bGUgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB0aXRsZSAtIFRoZSBkZXNpcmVkIHRpdGxlIG9mIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgU2V0VGl0bGUodGl0bGU6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFRpdGxlTWV0aG9kLCB7IHRpdGxlIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIHpvb20gbGV2ZWwgb2YgdGhlIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB6b29tIC0gVGhlIGRlc2lyZWQgem9vbSBsZXZlbC5cbiAgICAgKi9cbiAgICBTZXRab29tKHpvb206IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFNldFpvb21NZXRob2QsIHsgem9vbSB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNob3coKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2hvd01ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGN1cnJlbnQgc2l6ZSBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFNpemUoKTogUHJvbWlzZTxTaXplPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oU2l6ZU1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVG9nZ2xlcyB0aGUgd2luZG93IGJldHdlZW4gZnVsbHNjcmVlbiBhbmQgbm9ybWFsLlxuICAgICAqL1xuICAgIFRvZ2dsZUZ1bGxzY3JlZW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVG9nZ2xlRnVsbHNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVG9nZ2xlcyB0aGUgd2luZG93IGJldHdlZW4gbWF4aW1pc2VkIGFuZCBub3JtYWwuXG4gICAgICovXG4gICAgVG9nZ2xlTWF4aW1pc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oVG9nZ2xlTWF4aW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRvZ2dsZXMgdGhlIHdpbmRvdyBiZXR3ZWVuIGZyYW1lbGVzcyBhbmQgbm9ybWFsLlxuICAgICAqL1xuICAgIFRvZ2dsZUZyYW1lbGVzcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShUb2dnbGVGcmFtZWxlc3NNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVuLWZ1bGxzY3JlZW5zIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgVW5GdWxsc2NyZWVuKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFVuRnVsbHNjcmVlbk1ldGhvZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVW4tbWF4aW1pc2VzIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgVW5NYXhpbWlzZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShVbk1heGltaXNlTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBVbi1taW5pbWlzZXMgdGhlIHdpbmRvdy5cbiAgICAgKi9cbiAgICBVbk1pbmltaXNlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFVuTWluaW1pc2VNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHdpZHRoIG9mIHRoZSB3aW5kb3cuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgY3VycmVudCB3aWR0aCBvZiB0aGUgd2luZG93LlxuICAgICAqL1xuICAgIFdpZHRoKCk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oV2lkdGhNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFpvb21zIHRoZSB3aW5kb3cuXG4gICAgICovXG4gICAgWm9vbSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShab29tTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJbmNyZWFzZXMgdGhlIHpvb20gbGV2ZWwgb2YgdGhlIHdlYnZpZXcgY29udGVudC5cbiAgICAgKi9cbiAgICBab29tSW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJldHVybiB0aGlzW2NhbGxlclN5bV0oWm9vbUluTWV0aG9kKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEZWNyZWFzZXMgdGhlIHpvb20gbGV2ZWwgb2YgdGhlIHdlYnZpZXcgY29udGVudC5cbiAgICAgKi9cbiAgICBab29tT3V0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gdGhpc1tjYWxsZXJTeW1dKFpvb21PdXRNZXRob2QpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlc2V0cyB0aGUgem9vbSBsZXZlbCBvZiB0aGUgd2VidmlldyBjb250ZW50LlxuICAgICAqL1xuICAgIFpvb21SZXNldCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXNbY2FsbGVyU3ltXShab29tUmVzZXRNZXRob2QpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBUaGUgd2luZG93IHdpdGhpbiB3aGljaCB0aGUgc2NyaXB0IGlzIHJ1bm5pbmcuXG4gKi9cbmNvbnN0IHRoaXNXaW5kb3cgPSBuZXcgV2luZG93KCcnKTtcblxuZXhwb3J0IGRlZmF1bHQgdGhpc1dpbmRvdztcbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0ICogYXMgUnVudGltZSBmcm9tIFwiLi4vQHdhaWxzaW8vcnVudGltZS9zcmNcIjtcblxuLy8gTk9URTogdGhlIGZvbGxvd2luZyBtZXRob2RzIE1VU1QgYmUgaW1wb3J0ZWQgZXhwbGljaXRseSBiZWNhdXNlIG9mIGhvdyBlc2J1aWxkIGluamVjdGlvbiB3b3Jrc1xuaW1wb3J0IHsgRW5hYmxlIGFzIEVuYWJsZVdNTCB9IGZyb20gXCIuLi9Ad2FpbHNpby9ydW50aW1lL3NyYy93bWxcIjtcbmltcG9ydCB7IGRlYnVnTG9nIH0gZnJvbSBcIi4uL0B3YWlsc2lvL3J1bnRpbWUvc3JjL3V0aWxzXCI7XG5cbndpbmRvdy53YWlscyA9IFJ1bnRpbWU7XG5FbmFibGVXTUwoKTtcblxuaWYgKERFQlVHKSB7XG4gICAgZGVidWdMb2coXCJXYWlscyBSdW50aW1lIExvYWRlZFwiKVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lcyB9IGZyb20gXCIuL3J1bnRpbWUuanNcIjtcblxuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuU3lzdGVtKTtcblxuY29uc3QgU3lzdGVtSXNEYXJrTW9kZSA9IDA7XG5jb25zdCBTeXN0ZW1FbnZpcm9ubWVudCA9IDE7XG5cbmNvbnN0IF9pbnZva2UgPSAoZnVuY3Rpb24gKCkge1xuICAgIHRyeSB7XG4gICAgICAgIGlmICgod2luZG93IGFzIGFueSkuY2hyb21lPy53ZWJ2aWV3Py5wb3N0TWVzc2FnZSkge1xuICAgICAgICAgICAgcmV0dXJuICh3aW5kb3cgYXMgYW55KS5jaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZS5iaW5kKCh3aW5kb3cgYXMgYW55KS5jaHJvbWUud2Vidmlldyk7XG4gICAgICAgIH0gZWxzZSBpZiAoKHdpbmRvdyBhcyBhbnkpLndlYmtpdD8ubWVzc2FnZUhhbmRsZXJzPy5bJ2V4dGVybmFsJ10/LnBvc3RNZXNzYWdlKSB7XG4gICAgICAgICAgICByZXR1cm4gKHdpbmRvdyBhcyBhbnkpLndlYmtpdC5tZXNzYWdlSGFuZGxlcnNbJ2V4dGVybmFsJ10ucG9zdE1lc3NhZ2UuYmluZCgod2luZG93IGFzIGFueSkud2Via2l0Lm1lc3NhZ2VIYW5kbGVyc1snZXh0ZXJuYWwnXSk7XG4gICAgICAgIH1cbiAgICB9IGNhdGNoKGUpIHt9XG5cbiAgICBjb25zb2xlLndhcm4oJ1xcbiVjXHUyNkEwXHVGRTBGIEJyb3dzZXIgRW52aXJvbm1lbnQgRGV0ZWN0ZWQgJWNcXG5cXG4lY09ubHkgVUkgcHJldmlld3MgYXJlIGF2YWlsYWJsZSBpbiB0aGUgYnJvd3Nlci4gRm9yIGZ1bGwgZnVuY3Rpb25hbGl0eSwgcGxlYXNlIHJ1biB0aGUgYXBwbGljYXRpb24gaW4gZGVza3RvcCBtb2RlLlxcbk1vcmUgaW5mb3JtYXRpb24gYXQ6IGh0dHBzOi8vdjMud2FpbHMuaW8vbGVhcm4vYnVpbGQvI3VzaW5nLWEtYnJvd3Nlci1mb3ItZGV2ZWxvcG1lbnRcXG4nLFxuICAgICAgICAnYmFja2dyb3VuZDogI2ZmZmZmZjsgY29sb3I6ICMwMDAwMDA7IGZvbnQtd2VpZ2h0OiBib2xkOyBwYWRkaW5nOiA0cHggOHB4OyBib3JkZXItcmFkaXVzOiA0cHg7IGJvcmRlcjogMnB4IHNvbGlkICMwMDAwMDA7JyxcbiAgICAgICAgJ2JhY2tncm91bmQ6IHRyYW5zcGFyZW50OycsXG4gICAgICAgICdjb2xvcjogI2ZmZmZmZjsgZm9udC1zdHlsZTogaXRhbGljOyBmb250LXdlaWdodDogYm9sZDsnKTtcbiAgICByZXR1cm4gbnVsbDtcbn0pKCk7XG5cbmV4cG9ydCBmdW5jdGlvbiBpbnZva2UobXNnOiBhbnkpOiB2b2lkIHtcbiAgICBfaW52b2tlPy4obXNnKTtcbn1cblxuLyoqXG4gKiBSZXRyaWV2ZXMgdGhlIHN5c3RlbSBkYXJrIG1vZGUgc3RhdHVzLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGEgYm9vbGVhbiB2YWx1ZSBpbmRpY2F0aW5nIGlmIHRoZSBzeXN0ZW0gaXMgaW4gZGFyayBtb2RlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNEYXJrTW9kZSgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1Jc0RhcmtNb2RlKTtcbn1cblxuLyoqXG4gKiBGZXRjaGVzIHRoZSBjYXBhYmlsaXRpZXMgb2YgdGhlIGFwcGxpY2F0aW9uIGZyb20gdGhlIHNlcnZlci5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byBhbiBvYmplY3QgY29udGFpbmluZyB0aGUgY2FwYWJpbGl0aWVzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gQ2FwYWJpbGl0aWVzKCk6IFByb21pc2U8UmVjb3JkPHN0cmluZywgYW55Pj4ge1xuICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKFwiL3dhaWxzL2NhcGFiaWxpdGllc1wiKTtcbiAgICBpZiAocmVzcG9uc2Uub2spIHtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJjb3VsZCBub3QgZmV0Y2ggY2FwYWJpbGl0aWVzOiBcIiArIHJlc3BvbnNlLnN0YXR1c1RleHQpO1xuICAgIH1cbn1cblxuZXhwb3J0IGludGVyZmFjZSBPU0luZm8ge1xuICAgIC8qKiBUaGUgYnJhbmRpbmcgb2YgdGhlIE9TLiAqL1xuICAgIEJyYW5kaW5nOiBzdHJpbmc7XG4gICAgLyoqIFRoZSBJRCBvZiB0aGUgT1MuICovXG4gICAgSUQ6IHN0cmluZztcbiAgICAvKiogVGhlIG5hbWUgb2YgdGhlIE9TLiAqL1xuICAgIE5hbWU6IHN0cmluZztcbiAgICAvKiogVGhlIHZlcnNpb24gb2YgdGhlIE9TLiAqL1xuICAgIFZlcnNpb246IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFbnZpcm9ubWVudEluZm8ge1xuICAgIC8qKiBUaGUgYXJjaGl0ZWN0dXJlIG9mIHRoZSBzeXN0ZW0uICovXG4gICAgQXJjaDogc3RyaW5nO1xuICAgIC8qKiBUcnVlIGlmIHRoZSBhcHBsaWNhdGlvbiBpcyBydW5uaW5nIGluIGRlYnVnIG1vZGUsIG90aGVyd2lzZSBmYWxzZS4gKi9cbiAgICBEZWJ1ZzogYm9vbGVhbjtcbiAgICAvKiogVGhlIG9wZXJhdGluZyBzeXN0ZW0gaW4gdXNlLiAqL1xuICAgIE9TOiBzdHJpbmc7XG4gICAgLyoqIERldGFpbHMgb2YgdGhlIG9wZXJhdGluZyBzeXN0ZW0uICovXG4gICAgT1NJbmZvOiBPU0luZm87XG4gICAgLyoqIEFkZGl0aW9uYWwgcGxhdGZvcm0gaW5mb3JtYXRpb24uICovXG4gICAgUGxhdGZvcm1JbmZvOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xufVxuXG4vKipcbiAqIFJldHJpZXZlcyBlbnZpcm9ubWVudCBkZXRhaWxzLlxuICpcbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIGFuIG9iamVjdCBjb250YWluaW5nIE9TIGFuZCBzeXN0ZW0gYXJjaGl0ZWN0dXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKTogUHJvbWlzZTxFbnZpcm9ubWVudEluZm8+IHtcbiAgICByZXR1cm4gY2FsbChTeXN0ZW1FbnZpcm9ubWVudCk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIHRoZSBjdXJyZW50IG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cy5cbiAqXG4gKiBAcmV0dXJuIFRydWUgaWYgdGhlIG9wZXJhdGluZyBzeXN0ZW0gaXMgV2luZG93cywgb3RoZXJ3aXNlIGZhbHNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNXaW5kb3dzKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB3aW5kb3cuX3dhaWxzLmVudmlyb25tZW50Lk9TID09PSBcIndpbmRvd3NcIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgb3BlcmF0aW5nIHN5c3RlbSBpcyBMaW51eC5cbiAqXG4gKiBAcmV0dXJucyBSZXR1cm5zIHRydWUgaWYgdGhlIGN1cnJlbnQgb3BlcmF0aW5nIHN5c3RlbSBpcyBMaW51eCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNMaW51eCgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gd2luZG93Ll93YWlscy5lbnZpcm9ubWVudC5PUyA9PT0gXCJsaW51eFwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBpcyBhIG1hY09TIG9wZXJhdGluZyBzeXN0ZW0uXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgZW52aXJvbm1lbnQgaXMgbWFjT1MsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzTWFjKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB3aW5kb3cuX3dhaWxzLmVudmlyb25tZW50Lk9TID09PSBcImRhcndpblwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQuXG4gKlxuICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgY3VycmVudCBlbnZpcm9ubWVudCBhcmNoaXRlY3R1cmUgaXMgQU1ENjQsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQU1ENjQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHdpbmRvdy5fd2FpbHMuZW52aXJvbm1lbnQuQXJjaCA9PT0gXCJhbWQ2NFwiO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiB0aGUgY3VycmVudCBhcmNoaXRlY3R1cmUgaXMgQVJNLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGN1cnJlbnQgYXJjaGl0ZWN0dXJlIGlzIEFSTSwgZmFsc2Ugb3RoZXJ3aXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gSXNBUk0oKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHdpbmRvdy5fd2FpbHMuZW52aXJvbm1lbnQuQXJjaCA9PT0gXCJhcm1cIjtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGN1cnJlbnQgZW52aXJvbm1lbnQgaXMgQVJNNjQgYXJjaGl0ZWN0dXJlLlxuICpcbiAqIEByZXR1cm5zIFJldHVybnMgdHJ1ZSBpZiB0aGUgZW52aXJvbm1lbnQgaXMgQVJNNjQgYXJjaGl0ZWN0dXJlLCBvdGhlcndpc2UgcmV0dXJucyBmYWxzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzQVJNNjQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHdpbmRvdy5fd2FpbHMuZW52aXJvbm1lbnQuQXJjaCA9PT0gXCJhcm02NFwiO1xufVxuXG4vKipcbiAqIFJlcG9ydHMgd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIHJ1biBpbiBkZWJ1ZyBtb2RlLlxuICpcbiAqIEByZXR1cm5zIFRydWUgaWYgdGhlIGFwcCBpcyBiZWluZyBydW4gaW4gZGVidWcgbW9kZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzRGVidWcoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIEJvb2xlYW4od2luZG93Ll93YWlscy5lbnZpcm9ubWVudC5EZWJ1Zyk7XG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5pbXBvcnQgeyBJc0RlYnVnIH0gZnJvbSBcIi4vc3lzdGVtLmpzXCI7XG5pbXBvcnQgeyBldmVudFRhcmdldCB9IGZyb20gXCIuL3V0aWxzXCI7XG5cbi8vIHNldHVwXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5Db250ZXh0TWVudSk7XG5cbmNvbnN0IENvbnRleHRNZW51T3BlbiA9IDA7XG5cbmZ1bmN0aW9uIG9wZW5Db250ZXh0TWVudShpZDogc3RyaW5nLCB4OiBudW1iZXIsIHk6IG51bWJlciwgZGF0YTogYW55KTogdm9pZCB7XG4gICAgdm9pZCBjYWxsKENvbnRleHRNZW51T3Blbiwge2lkLCB4LCB5LCBkYXRhfSk7XG59XG5cbmZ1bmN0aW9uIGNvbnRleHRNZW51SGFuZGxlcihldmVudDogTW91c2VFdmVudCkge1xuICAgIGNvbnN0IHRhcmdldCA9IGV2ZW50VGFyZ2V0KGV2ZW50KTtcblxuICAgIC8vIENoZWNrIGZvciBjdXN0b20gY29udGV4dCBtZW51XG4gICAgY29uc3QgY3VzdG9tQ29udGV4dE1lbnUgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0YXJnZXQpLmdldFByb3BlcnR5VmFsdWUoXCItLWN1c3RvbS1jb250ZXh0bWVudVwiKS50cmltKCk7XG5cbiAgICBpZiAoY3VzdG9tQ29udGV4dE1lbnUpIHtcbiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgY29uc3QgZGF0YSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tY3VzdG9tLWNvbnRleHRtZW51LWRhdGFcIik7XG4gICAgICAgIG9wZW5Db250ZXh0TWVudShjdXN0b21Db250ZXh0TWVudSwgZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSwgZGF0YSk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcHJvY2Vzc0RlZmF1bHRDb250ZXh0TWVudShldmVudCwgdGFyZ2V0KTtcbiAgICB9XG59XG5cblxuLypcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogYXV0bzsgKGRlZmF1bHQpIHdpbGwgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnUgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWUgT1IgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBPUiBlbGVtZW50IGlzIGlucHV0IG9yIHRleHRhcmVhXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IHNob3c7IHdpbGwgYWx3YXlzIHNob3cgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG5cblRoaXMgcnVsZSBpcyBpbmhlcml0ZWQgbGlrZSBub3JtYWwgQ1NTIHJ1bGVzLCBzbyBuZXN0aW5nIHdvcmtzIGFzIGV4cGVjdGVkXG4qL1xuZnVuY3Rpb24gcHJvY2Vzc0RlZmF1bHRDb250ZXh0TWVudShldmVudDogTW91c2VFdmVudCwgdGFyZ2V0OiBIVE1MRWxlbWVudCkge1xuICAgIC8vIERlYnVnIGJ1aWxkcyBhbHdheXMgc2hvdyB0aGUgbWVudVxuICAgIGlmIChJc0RlYnVnKCkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIFByb2Nlc3MgZGVmYXVsdCBjb250ZXh0IG1lbnVcbiAgICBzd2l0Y2ggKHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZShcIi0tZGVmYXVsdC1jb250ZXh0bWVudVwiKS50cmltKCkpIHtcbiAgICAgICAgY2FzZSAnc2hvdyc6XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNhc2UgJ2hpZGUnOlxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiBjb250ZW50RWRpdGFibGUgaXMgdHJ1ZVxuICAgIGlmICh0YXJnZXQuaXNDb250ZW50RWRpdGFibGUpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGlmIHRleHQgaGFzIGJlZW4gc2VsZWN0ZWRcbiAgICBjb25zdCBzZWxlY3Rpb24gPSB3aW5kb3cuZ2V0U2VsZWN0aW9uKCk7XG4gICAgY29uc3QgaGFzU2VsZWN0aW9uID0gc2VsZWN0aW9uICYmIHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDA7XG4gICAgaWYgKGhhc1NlbGVjdGlvbikge1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNlbGVjdGlvbi5yYW5nZUNvdW50OyBpKyspIHtcbiAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XG4gICAgICAgICAgICBjb25zdCByZWN0cyA9IHJhbmdlLmdldENsaWVudFJlY3RzKCk7XG4gICAgICAgICAgICBmb3IgKGxldCBqID0gMDsgaiA8IHJlY3RzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xuICAgICAgICAgICAgICAgIGlmIChkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHJlY3QubGVmdCwgcmVjdC50b3ApID09PSB0YXJnZXQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIENoZWNrIGlmIHRhZyBpcyBpbnB1dCBvciB0ZXh0YXJlYS5cbiAgICBpZiAodGFyZ2V0IGluc3RhbmNlb2YgSFRNTElucHV0RWxlbWVudCB8fCB0YXJnZXQgaW5zdGFuY2VvZiBIVE1MVGV4dEFyZWFFbGVtZW50KSB7XG4gICAgICAgIGlmIChoYXNTZWxlY3Rpb24gfHwgKCF0YXJnZXQucmVhZE9ubHkgJiYgIXRhcmdldC5kaXNhYmxlZCkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIGhpZGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcbiAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKipcbiAqIFJldHJpZXZlcyB0aGUgdmFsdWUgYXNzb2NpYXRlZCB3aXRoIHRoZSBzcGVjaWZpZWQga2V5IGZyb20gdGhlIGZsYWcgbWFwLlxuICpcbiAqIEBwYXJhbSBrZXkgLSBUaGUga2V5IHRvIHJldHJpZXZlIHRoZSB2YWx1ZSBmb3IuXG4gKiBAcmV0dXJuIFRoZSB2YWx1ZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNwZWNpZmllZCBrZXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRGbGFnKGtleTogc3RyaW5nKTogYW55IHtcbiAgICB0cnkge1xuICAgICAgICByZXR1cm4gd2luZG93Ll93YWlscy5mbGFnc1trZXldO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVW5hYmxlIHRvIHJldHJpZXZlIGZsYWcgJ1wiICsga2V5ICsgXCInOiBcIiArIGUsIHsgY2F1c2U6IGUgfSk7XG4gICAgfVxufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5pbXBvcnQgeyBpbnZva2UsIElzV2luZG93cyB9IGZyb20gXCIuL3N5c3RlbS5qc1wiO1xuaW1wb3J0IHsgR2V0RmxhZyB9IGZyb20gXCIuL2ZsYWdzLmpzXCI7XG5pbXBvcnQgeyBjYW5UcmFja0J1dHRvbnMsIGV2ZW50VGFyZ2V0IH0gZnJvbSBcIi4vdXRpbHMuanNcIjtcblxuLy8gU2V0dXBcbmxldCBjYW5EcmFnID0gZmFsc2U7XG5sZXQgZHJhZ2dpbmcgPSBmYWxzZTtcblxubGV0IHJlc2l6YWJsZSA9IGZhbHNlO1xubGV0IGNhblJlc2l6ZSA9IGZhbHNlO1xubGV0IHJlc2l6aW5nID0gZmFsc2U7XG5sZXQgcmVzaXplRWRnZTogc3RyaW5nID0gXCJcIjtcbmxldCBkZWZhdWx0Q3Vyc29yID0gXCJhdXRvXCI7XG5cbmxldCBidXR0b25zID0gMDtcbmNvbnN0IGJ1dHRvbnNUcmFja2VkID0gY2FuVHJhY2tCdXR0b25zKCk7XG5cbndpbmRvdy5fd2FpbHMgPSB3aW5kb3cuX3dhaWxzIHx8IHt9O1xud2luZG93Ll93YWlscy5zZXRSZXNpemFibGUgPSAodmFsdWU6IGJvb2xlYW4pOiB2b2lkID0+IHtcbiAgICByZXNpemFibGUgPSB2YWx1ZTtcbiAgICBpZiAoIXJlc2l6YWJsZSkge1xuICAgICAgICAvLyBTdG9wIHJlc2l6aW5nIGlmIGluIHByb2dyZXNzLlxuICAgICAgICBjYW5SZXNpemUgPSByZXNpemluZyA9IGZhbHNlO1xuICAgICAgICBzZXRSZXNpemUoKTtcbiAgICB9XG59O1xuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vkb3duJywgdXBkYXRlLCB7IGNhcHR1cmU6IHRydWUgfSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vtb3ZlJywgdXBkYXRlLCB7IGNhcHR1cmU6IHRydWUgfSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2V1cCcsIHVwZGF0ZSwgeyBjYXB0dXJlOiB0cnVlIH0pO1xuZm9yIChjb25zdCBldiBvZiBbJ2NsaWNrJywgJ2NvbnRleHRtZW51JywgJ2RibGNsaWNrJ10pIHtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihldiwgc3VwcHJlc3NFdmVudCwgeyBjYXB0dXJlOiB0cnVlIH0pO1xufVxuXG5mdW5jdGlvbiBzdXBwcmVzc0V2ZW50KGV2ZW50OiBFdmVudCkge1xuICAgIC8vIFN1cHByZXNzIGNsaWNrIGV2ZW50cyB3aGlsZSByZXNpemluZyBvciBkcmFnZ2luZy5cbiAgICBpZiAoZHJhZ2dpbmcgfHwgcmVzaXppbmcpIHtcbiAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH1cbn1cblxuLy8gVXNlIGNvbnN0YW50cyB0byBhdm9pZCBjb21wYXJpbmcgc3RyaW5ncyBtdWx0aXBsZSB0aW1lcy5cbmNvbnN0IE1vdXNlRG93biA9IDA7XG5jb25zdCBNb3VzZVVwICAgPSAxO1xuY29uc3QgTW91c2VNb3ZlID0gMjtcblxuZnVuY3Rpb24gdXBkYXRlKGV2ZW50OiBNb3VzZUV2ZW50KSB7XG4gICAgLy8gV2luZG93cyBzdXBwcmVzc2VzIG1vdXNlIGV2ZW50cyBhdCB0aGUgZW5kIG9mIGRyYWdnaW5nIG9yIHJlc2l6aW5nLFxuICAgIC8vIHNvIHdlIG5lZWQgdG8gYmUgc21hcnQgYW5kIHN5bnRoZXNpemUgYnV0dG9uIGV2ZW50cy5cblxuICAgIGxldCBldmVudFR5cGU6IG51bWJlciwgZXZlbnRCdXR0b25zID0gZXZlbnQuYnV0dG9ucztcbiAgICBzd2l0Y2ggKGV2ZW50LnR5cGUpIHtcbiAgICAgICAgY2FzZSAnbW91c2Vkb3duJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlRG93bjtcbiAgICAgICAgICAgIGlmICghYnV0dG9uc1RyYWNrZWQpIHsgZXZlbnRCdXR0b25zID0gYnV0dG9ucyB8ICgxIDw8IGV2ZW50LmJ1dHRvbik7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdtb3VzZXVwJzpcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IE1vdXNlVXA7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnMgJiB+KDEgPDwgZXZlbnQuYnV0dG9uKTsgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICBldmVudFR5cGUgPSBNb3VzZU1vdmU7XG4gICAgICAgICAgICBpZiAoIWJ1dHRvbnNUcmFja2VkKSB7IGV2ZW50QnV0dG9ucyA9IGJ1dHRvbnM7IH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGxldCByZWxlYXNlZCA9IGJ1dHRvbnMgJiB+ZXZlbnRCdXR0b25zO1xuICAgIGxldCBwcmVzc2VkID0gZXZlbnRCdXR0b25zICYgfmJ1dHRvbnM7XG5cbiAgICBidXR0b25zID0gZXZlbnRCdXR0b25zO1xuXG4gICAgLy8gU3ludGhlc2l6ZSBhIHJlbGVhc2UtcHJlc3Mgc2VxdWVuY2UgaWYgd2UgZGV0ZWN0IGEgcHJlc3Mgb2YgYW4gYWxyZWFkeSBwcmVzc2VkIGJ1dHRvbi5cbiAgICBpZiAoZXZlbnRUeXBlID09PSBNb3VzZURvd24gJiYgIShwcmVzc2VkICYgZXZlbnQuYnV0dG9uKSkge1xuICAgICAgICByZWxlYXNlZCB8PSAoMSA8PCBldmVudC5idXR0b24pO1xuICAgICAgICBwcmVzc2VkIHw9ICgxIDw8IGV2ZW50LmJ1dHRvbik7XG4gICAgfVxuXG4gICAgLy8gU3VwcHJlc3MgYWxsIGJ1dHRvbiBldmVudHMgZHVyaW5nIGRyYWdnaW5nIGFuZCByZXNpemluZyxcbiAgICAvLyB1bmxlc3MgdGhpcyBpcyBhIG1vdXNldXAgZXZlbnQgdGhhdCBpcyBlbmRpbmcgYSBkcmFnIGFjdGlvbi5cbiAgICBpZiAoXG4gICAgICAgIGV2ZW50VHlwZSAhPT0gTW91c2VNb3ZlIC8vIEZhc3QgcGF0aCBmb3IgbW91c2Vtb3ZlXG4gICAgICAgICYmIHJlc2l6aW5nXG4gICAgICAgIHx8IChcbiAgICAgICAgICAgIGRyYWdnaW5nXG4gICAgICAgICAgICAmJiAoXG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlID09PSBNb3VzZURvd25cbiAgICAgICAgICAgICAgICB8fCBldmVudC5idXR0b24gIT09IDBcbiAgICAgICAgICAgIClcbiAgICAgICAgKVxuICAgICkge1xuICAgICAgICBldmVudC5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKTtcbiAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgfVxuXG4gICAgLy8gSGFuZGxlIHJlbGVhc2VzXG4gICAgaWYgKHJlbGVhc2VkICYgMSkgeyBwcmltYXJ5VXAoZXZlbnQpOyB9XG4gICAgLy8gSGFuZGxlIHByZXNzZXNcbiAgICBpZiAocHJlc3NlZCAmIDEpIHsgcHJpbWFyeURvd24oZXZlbnQpOyB9XG5cbiAgICAvLyBIYW5kbGUgbW91c2Vtb3ZlXG4gICAgaWYgKGV2ZW50VHlwZSA9PT0gTW91c2VNb3ZlKSB7IG9uTW91c2VNb3ZlKGV2ZW50KTsgfTtcbn1cblxuZnVuY3Rpb24gcHJpbWFyeURvd24oZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICAvLyBSZXNldCByZWFkaW5lc3Mgc3RhdGUuXG4gICAgY2FuRHJhZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuXG4gICAgLy8gSWdub3JlIHJlcGVhdGVkIGNsaWNrcyBvbiBtYWNPUyBhbmQgTGludXguXG4gICAgaWYgKCFJc1dpbmRvd3MoKSkge1xuICAgICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ21vdXNlZG93bicgJiYgZXZlbnQuYnV0dG9uID09PSAwICYmIGV2ZW50LmRldGFpbCAhPT0gMSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gUmVhZHkgdG8gcmVzaXplIGlmIHRoZSBwcmltYXJ5IGJ1dHRvbiB3YXMgcHJlc3NlZCBmb3IgdGhlIGZpcnN0IHRpbWUuXG4gICAgICAgIGNhblJlc2l6ZSA9IHRydWU7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnIG9wZXJhdGlvbnMgd2hlbiBvbiByZXNpemUgZWRnZXMuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBSZXRyaWV2ZSB0YXJnZXQgZWxlbWVudFxuICAgIGNvbnN0IHRhcmdldCA9IGV2ZW50VGFyZ2V0KGV2ZW50KTtcblxuICAgIC8vIFJlYWR5IHRvIGRyYWcgaWYgdGhlIHByaW1hcnkgYnV0dG9uIHdhcyBwcmVzc2VkIGZvciB0aGUgZmlyc3QgdGltZSBvbiBhIGRyYWdnYWJsZSBlbGVtZW50LlxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gdGhlIHNjcm9sbGJhci5cbiAgICBjb25zdCBzdHlsZSA9IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRhcmdldCk7XG4gICAgY2FuRHJhZyA9IChcbiAgICAgICAgc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShcIi0td2FpbHMtZHJhZ2dhYmxlXCIpLnRyaW0oKSA9PT0gXCJkcmFnXCJcbiAgICAgICAgJiYgKFxuICAgICAgICAgICAgZXZlbnQub2Zmc2V0WCAtIHBhcnNlRmxvYXQoc3R5bGUucGFkZGluZ0xlZnQpIDwgdGFyZ2V0LmNsaWVudFdpZHRoXG4gICAgICAgICAgICAmJiBldmVudC5vZmZzZXRZIC0gcGFyc2VGbG9hdChzdHlsZS5wYWRkaW5nVG9wKSA8IHRhcmdldC5jbGllbnRIZWlnaHRcbiAgICAgICAgKVxuICAgICk7XG59XG5cbmZ1bmN0aW9uIHByaW1hcnlVcChldmVudDogTW91c2VFdmVudCkge1xuICAgIC8vIFN0b3AgZHJhZ2dpbmcgYW5kIHJlc2l6aW5nLlxuICAgIGNhbkRyYWcgPSBmYWxzZTtcbiAgICBkcmFnZ2luZyA9IGZhbHNlO1xuICAgIGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgIHJlc2l6aW5nID0gZmFsc2U7XG59XG5cbmNvbnN0IGN1cnNvckZvckVkZ2UgPSBPYmplY3QuZnJlZXplKHtcbiAgICBcInNlLXJlc2l6ZVwiOiBcIm53c2UtcmVzaXplXCIsXG4gICAgXCJzdy1yZXNpemVcIjogXCJuZXN3LXJlc2l6ZVwiLFxuICAgIFwibnctcmVzaXplXCI6IFwibndzZS1yZXNpemVcIixcbiAgICBcIm5lLXJlc2l6ZVwiOiBcIm5lc3ctcmVzaXplXCIsXG4gICAgXCJ3LXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxuICAgIFwibi1yZXNpemVcIjogXCJucy1yZXNpemVcIixcbiAgICBcInMtcmVzaXplXCI6IFwibnMtcmVzaXplXCIsXG4gICAgXCJlLXJlc2l6ZVwiOiBcImV3LXJlc2l6ZVwiLFxufSlcblxuZnVuY3Rpb24gc2V0UmVzaXplKGVkZ2U/OiBrZXlvZiB0eXBlb2YgY3Vyc29yRm9yRWRnZSk6IHZvaWQge1xuICAgIGlmIChlZGdlKSB7XG4gICAgICAgIGlmICghcmVzaXplRWRnZSkgeyBkZWZhdWx0Q3Vyc29yID0gZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3I7IH1cbiAgICAgICAgZG9jdW1lbnQuYm9keS5zdHlsZS5jdXJzb3IgPSBjdXJzb3JGb3JFZGdlW2VkZ2VdO1xuICAgIH0gZWxzZSBpZiAoIWVkZ2UgJiYgcmVzaXplRWRnZSkge1xuICAgICAgICBkb2N1bWVudC5ib2R5LnN0eWxlLmN1cnNvciA9IGRlZmF1bHRDdXJzb3I7XG4gICAgfVxuXG4gICAgcmVzaXplRWRnZSA9IGVkZ2UgfHwgXCJcIjtcbn1cblxuZnVuY3Rpb24gb25Nb3VzZU1vdmUoZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoY2FuUmVzaXplICYmIHJlc2l6ZUVkZ2UpIHtcbiAgICAgICAgLy8gU3RhcnQgcmVzaXppbmcuXG4gICAgICAgIHJlc2l6aW5nID0gdHJ1ZTtcbiAgICAgICAgaW52b2tlKFwid2FpbHM6cmVzaXplOlwiICsgcmVzaXplRWRnZSk7XG4gICAgfSBlbHNlIGlmIChjYW5EcmFnKSB7XG4gICAgICAgIC8vIFN0YXJ0IGRyYWdnaW5nLlxuICAgICAgICBkcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIGludm9rZShcIndhaWxzOmRyYWdcIik7XG4gICAgfVxuXG4gICAgaWYgKGRyYWdnaW5nIHx8IHJlc2l6aW5nKSB7XG4gICAgICAgIC8vIEVpdGhlciBkcmFnIG9yIHJlc2l6ZSBpcyBvbmdvaW5nLFxuICAgICAgICAvLyByZXNldCByZWFkaW5lc3MgYW5kIHN0b3AgcHJvY2Vzc2luZy5cbiAgICAgICAgY2FuRHJhZyA9IGNhblJlc2l6ZSA9IGZhbHNlO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFyZXNpemFibGUgfHwgIUlzV2luZG93cygpKSB7XG4gICAgICAgIGlmIChyZXNpemVFZGdlKSB7IHNldFJlc2l6ZSgpOyB9XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCByZXNpemVIYW5kbGVIZWlnaHQgPSBHZXRGbGFnKFwic3lzdGVtLnJlc2l6ZUhhbmRsZUhlaWdodFwiKSB8fCA1O1xuICAgIGNvbnN0IHJlc2l6ZUhhbmRsZVdpZHRoID0gR2V0RmxhZyhcInN5c3RlbS5yZXNpemVIYW5kbGVXaWR0aFwiKSB8fCA1O1xuXG4gICAgLy8gRXh0cmEgcGl4ZWxzIGZvciB0aGUgY29ybmVyIGFyZWFzLlxuICAgIGNvbnN0IGNvcm5lckV4dHJhID0gR2V0RmxhZyhcInJlc2l6ZUNvcm5lckV4dHJhXCIpIHx8IDEwO1xuXG4gICAgY29uc3QgcmlnaHRCb3JkZXIgPSAod2luZG93Lm91dGVyV2lkdGggLSBldmVudC5jbGllbnRYKSA8IHJlc2l6ZUhhbmRsZVdpZHRoO1xuICAgIGNvbnN0IGxlZnRCb3JkZXIgPSBldmVudC5jbGllbnRYIDwgcmVzaXplSGFuZGxlV2lkdGg7XG4gICAgY29uc3QgdG9wQm9yZGVyID0gZXZlbnQuY2xpZW50WSA8IHJlc2l6ZUhhbmRsZUhlaWdodDtcbiAgICBjb25zdCBib3R0b21Cb3JkZXIgPSAod2luZG93Lm91dGVySGVpZ2h0IC0gZXZlbnQuY2xpZW50WSkgPCByZXNpemVIYW5kbGVIZWlnaHQ7XG5cbiAgICAvLyBBZGp1c3QgZm9yIGNvcm5lciBhcmVhcy5cbiAgICBjb25zdCByaWdodENvcm5lciA9ICh3aW5kb3cub3V0ZXJXaWR0aCAtIGV2ZW50LmNsaWVudFgpIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGxlZnRDb3JuZXIgPSBldmVudC5jbGllbnRYIDwgKHJlc2l6ZUhhbmRsZVdpZHRoICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IHRvcENvcm5lciA9IGV2ZW50LmNsaWVudFkgPCAocmVzaXplSGFuZGxlSGVpZ2h0ICsgY29ybmVyRXh0cmEpO1xuICAgIGNvbnN0IGJvdHRvbUNvcm5lciA9ICh3aW5kb3cub3V0ZXJIZWlnaHQgLSBldmVudC5jbGllbnRZKSA8IChyZXNpemVIYW5kbGVIZWlnaHQgKyBjb3JuZXJFeHRyYSk7XG5cbiAgICBpZiAoIWxlZnRDb3JuZXIgJiYgIXRvcENvcm5lciAmJiAhYm90dG9tQ29ybmVyICYmICFyaWdodENvcm5lcikge1xuICAgICAgICAvLyBPcHRpbWlzYXRpb246IG91dCBvZiBhbGwgY29ybmVyIGFyZWFzIGltcGxpZXMgb3V0IG9mIGJvcmRlcnMuXG4gICAgICAgIHNldFJlc2l6ZSgpO1xuICAgIH1cbiAgICAvLyBEZXRlY3QgY29ybmVycy5cbiAgICBlbHNlIGlmIChyaWdodENvcm5lciAmJiBib3R0b21Db3JuZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Q29ybmVyICYmIGJvdHRvbUNvcm5lcikgc2V0UmVzaXplKFwic3ctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRDb3JuZXIgJiYgdG9wQ29ybmVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQ29ybmVyICYmIHJpZ2h0Q29ybmVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XG4gICAgLy8gRGV0ZWN0IGJvcmRlcnMuXG4gICAgZWxzZSBpZiAobGVmdEJvcmRlcikgc2V0UmVzaXplKFwidy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJlLXJlc2l6ZVwiKTtcbiAgICAvLyBPdXQgb2YgYm9yZGVyIGFyZWEuXG4gICAgZWxzZSBzZXRSZXNpemUoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHsgbmV3UnVudGltZUNhbGxlciwgb2JqZWN0TmFtZXMgfSBmcm9tIFwiLi9ydW50aW1lLmpzXCI7XG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5BcHBsaWNhdGlvbik7XG5cbmNvbnN0IEhpZGVNZXRob2QgPSAwO1xuY29uc3QgU2hvd01ldGhvZCA9IDE7XG5jb25zdCBRdWl0TWV0aG9kID0gMjtcblxuLyoqXG4gKiBIaWRlcyBhIGNlcnRhaW4gbWV0aG9kIGJ5IGNhbGxpbmcgdGhlIEhpZGVNZXRob2QgZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKEhpZGVNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBTaG93TWV0aG9kIGFuZCByZXR1cm5zIHRoZSByZXN1bHQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTaG93KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBjYWxsKFNob3dNZXRob2QpO1xufVxuXG4vKipcbiAqIENhbGxzIHRoZSBRdWl0TWV0aG9kIHRvIHRlcm1pbmF0ZSB0aGUgcHJvZ3JhbS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoUXVpdE1ldGhvZCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbmltcG9ydCB7IENhbmNlbGxhYmxlUHJvbWlzZSwgdHlwZSBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzIH0gZnJvbSBcIi4vY2FuY2VsbGFibGUuanNcIjtcbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuaW1wb3J0IHsgbmFub2lkIH0gZnJvbSBcIi4vbmFub2lkLmpzXCI7XG5cbi8vIFNldHVwXG53aW5kb3cuX3dhaWxzID0gd2luZG93Ll93YWlscyB8fCB7fTtcbndpbmRvdy5fd2FpbHMuY2FsbFJlc3VsdEhhbmRsZXIgPSByZXN1bHRIYW5kbGVyO1xud2luZG93Ll93YWlscy5jYWxsRXJyb3JIYW5kbGVyID0gZXJyb3JIYW5kbGVyO1xuXG50eXBlIFByb21pc2VSZXNvbHZlcnMgPSBPbWl0PENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8YW55PiwgXCJwcm9taXNlXCIgfCBcIm9uY2FuY2VsbGVkXCI+XG5cbmNvbnN0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdE5hbWVzLkNhbGwpO1xuY29uc3QgY2FuY2VsQ2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuQ2FuY2VsQ2FsbCk7XG5jb25zdCBjYWxsUmVzcG9uc2VzID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2VSZXNvbHZlcnM+KCk7XG5cbmNvbnN0IENhbGxCaW5kaW5nID0gMDtcbmNvbnN0IENhbmNlbE1ldGhvZCA9IDBcblxuLyoqXG4gKiBIb2xkcyBhbGwgcmVxdWlyZWQgaW5mb3JtYXRpb24gZm9yIGEgYmluZGluZyBjYWxsLlxuICogTWF5IHByb3ZpZGUgZWl0aGVyIGEgbWV0aG9kIElEIG9yIGEgbWV0aG9kIG5hbWUsIGJ1dCBub3QgYm90aC5cbiAqL1xuZXhwb3J0IHR5cGUgQ2FsbE9wdGlvbnMgPSB7XG4gICAgLyoqIFRoZSBudW1lcmljIElEIG9mIHRoZSBib3VuZCBtZXRob2QgdG8gY2FsbC4gKi9cbiAgICBtZXRob2RJRDogbnVtYmVyO1xuICAgIC8qKiBUaGUgZnVsbHkgcXVhbGlmaWVkIG5hbWUgb2YgdGhlIGJvdW5kIG1ldGhvZCB0byBjYWxsLiAqL1xuICAgIG1ldGhvZE5hbWU/OiBuZXZlcjtcbiAgICAvKiogQXJndW1lbnRzIHRvIGJlIHBhc3NlZCBpbnRvIHRoZSBib3VuZCBtZXRob2QuICovXG4gICAgYXJnczogYW55W107XG59IHwge1xuICAgIC8qKiBUaGUgbnVtZXJpYyBJRCBvZiB0aGUgYm91bmQgbWV0aG9kIHRvIGNhbGwuICovXG4gICAgbWV0aG9kSUQ/OiBuZXZlcjtcbiAgICAvKiogVGhlIGZ1bGx5IHF1YWxpZmllZCBuYW1lIG9mIHRoZSBib3VuZCBtZXRob2QgdG8gY2FsbC4gKi9cbiAgICBtZXRob2ROYW1lOiBzdHJpbmc7XG4gICAgLyoqIEFyZ3VtZW50cyB0byBiZSBwYXNzZWQgaW50byB0aGUgYm91bmQgbWV0aG9kLiAqL1xuICAgIGFyZ3M6IGFueVtdO1xufTtcblxuLyoqXG4gKiBFeGNlcHRpb24gY2xhc3MgdGhhdCB3aWxsIGJlIHRocm93biBpbiBjYXNlIHRoZSBib3VuZCBtZXRob2QgcmV0dXJucyBhbiBlcnJvci5cbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIFJ1bnRpbWVFcnJvciNuYW1lfSBwcm9wZXJ0eSBpcyBcIlJ1bnRpbWVFcnJvclwiLlxuICovXG5leHBvcnQgY2xhc3MgUnVudGltZUVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAgIC8qKlxuICAgICAqIENvbnN0cnVjdHMgYSBuZXcgUnVudGltZUVycm9yIGluc3RhbmNlLlxuICAgICAqIEBwYXJhbSBtZXNzYWdlIC0gVGhlIGVycm9yIG1lc3NhZ2UuXG4gICAgICogQHBhcmFtIG9wdGlvbnMgLSBPcHRpb25zIHRvIGJlIGZvcndhcmRlZCB0byB0aGUgRXJyb3IgY29uc3RydWN0b3IuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IobWVzc2FnZT86IHN0cmluZywgb3B0aW9ucz86IEVycm9yT3B0aW9ucykge1xuICAgICAgICBzdXBlcihtZXNzYWdlLCBvcHRpb25zKTtcbiAgICAgICAgdGhpcy5uYW1lID0gXCJSdW50aW1lRXJyb3JcIjtcbiAgICB9XG59XG5cbi8qKlxuICogSGFuZGxlcyB0aGUgcmVzdWx0IG9mIGEgY2FsbCByZXF1ZXN0LlxuICpcbiAqIEBwYXJhbSBpZCAtIFRoZSBpZCBvZiB0aGUgcmVxdWVzdCB0byBoYW5kbGUgdGhlIHJlc3VsdCBmb3IuXG4gKiBAcGFyYW0gZGF0YSAtIFRoZSByZXN1bHQgZGF0YSBvZiB0aGUgcmVxdWVzdC5cbiAqIEBwYXJhbSBpc0pTT04gLSBJbmRpY2F0ZXMgd2hldGhlciB0aGUgZGF0YSBpcyBKU09OIG9yIG5vdC5cbiAqL1xuZnVuY3Rpb24gcmVzdWx0SGFuZGxlcihpZDogc3RyaW5nLCBkYXRhOiBzdHJpbmcsIGlzSlNPTjogYm9vbGVhbik6IHZvaWQge1xuICAgIGNvbnN0IHJlc29sdmVycyA9IGdldEFuZERlbGV0ZVJlc3BvbnNlKGlkKTtcbiAgICBpZiAoIXJlc29sdmVycykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFkYXRhKSB7XG4gICAgICAgIHJlc29sdmVycy5yZXNvbHZlKHVuZGVmaW5lZCk7XG4gICAgfSBlbHNlIGlmICghaXNKU09OKSB7XG4gICAgICAgIHJlc29sdmVycy5yZXNvbHZlKGRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXNvbHZlcnMucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyOiBhbnkpIHtcbiAgICAgICAgICAgIHJlc29sdmVycy5yZWplY3QobmV3IFR5cGVFcnJvcihcImNvdWxkIG5vdCBwYXJzZSByZXN1bHQ6IFwiICsgZXJyLm1lc3NhZ2UsIHsgY2F1c2U6IGVyciB9KSk7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi8qKlxuICogSGFuZGxlcyB0aGUgZXJyb3IgZnJvbSBhIGNhbGwgcmVxdWVzdC5cbiAqXG4gKiBAcGFyYW0gaWQgLSBUaGUgaWQgb2YgdGhlIHByb21pc2UgaGFuZGxlci5cbiAqIEBwYXJhbSBkYXRhIC0gVGhlIGVycm9yIGRhdGEgdG8gcmVqZWN0IHRoZSBwcm9taXNlIGhhbmRsZXIgd2l0aC5cbiAqIEBwYXJhbSBpc0pTT04gLSBJbmRpY2F0ZXMgd2hldGhlciB0aGUgZGF0YSBpcyBKU09OIG9yIG5vdC5cbiAqL1xuZnVuY3Rpb24gZXJyb3JIYW5kbGVyKGlkOiBzdHJpbmcsIGRhdGE6IHN0cmluZywgaXNKU09OOiBib29sZWFuKTogdm9pZCB7XG4gICAgY29uc3QgcmVzb2x2ZXJzID0gZ2V0QW5kRGVsZXRlUmVzcG9uc2UoaWQpO1xuICAgIGlmICghcmVzb2x2ZXJzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoIWlzSlNPTikge1xuICAgICAgICByZXNvbHZlcnMucmVqZWN0KG5ldyBFcnJvcihkYXRhKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgbGV0IGVycm9yOiBhbnk7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBlcnJvciA9IEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycjogYW55KSB7XG4gICAgICAgICAgICByZXNvbHZlcnMucmVqZWN0KG5ldyBUeXBlRXJyb3IoXCJjb3VsZCBub3QgcGFyc2UgZXJyb3I6IFwiICsgZXJyLm1lc3NhZ2UsIHsgY2F1c2U6IGVyciB9KSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgb3B0aW9uczogRXJyb3JPcHRpb25zID0ge307XG4gICAgICAgIGlmIChlcnJvci5jYXVzZSkge1xuICAgICAgICAgICAgb3B0aW9ucy5jYXVzZSA9IGVycm9yLmNhdXNlO1xuICAgICAgICB9XG5cbiAgICAgICAgbGV0IGV4Y2VwdGlvbjtcbiAgICAgICAgc3dpdGNoIChlcnJvci5raW5kKSB7XG4gICAgICAgICAgICBjYXNlIFwiUmVmZXJlbmNlRXJyb3JcIjpcbiAgICAgICAgICAgICAgICBleGNlcHRpb24gPSBuZXcgUmVmZXJlbmNlRXJyb3IoZXJyb3IubWVzc2FnZSwgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwiVHlwZUVycm9yXCI6XG4gICAgICAgICAgICAgICAgZXhjZXB0aW9uID0gbmV3IFR5cGVFcnJvcihlcnJvci5tZXNzYWdlLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgXCJSdW50aW1lRXJyb3JcIjpcbiAgICAgICAgICAgICAgICBleGNlcHRpb24gPSBuZXcgUnVudGltZUVycm9yKGVycm9yLm1lc3NhZ2UsIG9wdGlvbnMpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgICAgICBleGNlcHRpb24gPSBuZXcgRXJyb3IoZXJyb3IubWVzc2FnZSwgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgICByZXNvbHZlcnMucmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHJpZXZlcyBhbmQgcmVtb3ZlcyB0aGUgcmVzcG9uc2UgYXNzb2NpYXRlZCB3aXRoIHRoZSBnaXZlbiBJRCBmcm9tIHRoZSBjYWxsUmVzcG9uc2VzIG1hcC5cbiAqXG4gKiBAcGFyYW0gaWQgLSBUaGUgSUQgb2YgdGhlIHJlc3BvbnNlIHRvIGJlIHJldHJpZXZlZCBhbmQgcmVtb3ZlZC5cbiAqIEByZXR1cm5zIFRoZSByZXNwb25zZSBvYmplY3QgYXNzb2NpYXRlZCB3aXRoIHRoZSBnaXZlbiBJRCwgaWYgYW55LlxuICovXG5mdW5jdGlvbiBnZXRBbmREZWxldGVSZXNwb25zZShpZDogc3RyaW5nKTogUHJvbWlzZVJlc29sdmVycyB8IHVuZGVmaW5lZCB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBjYWxsUmVzcG9uc2VzLmdldChpZCk7XG4gICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIHJldHVybiByZXNwb25zZTtcbn1cblxuLyoqXG4gKiBHZW5lcmF0ZXMgYSB1bmlxdWUgSUQgdXNpbmcgdGhlIG5hbm9pZCBsaWJyYXJ5LlxuICpcbiAqIEByZXR1cm5zIEEgdW5pcXVlIElEIHRoYXQgZG9lcyBub3QgZXhpc3QgaW4gdGhlIGNhbGxSZXNwb25zZXMgc2V0LlxuICovXG5mdW5jdGlvbiBnZW5lcmF0ZUlEKCk6IHN0cmluZyB7XG4gICAgbGV0IHJlc3VsdDtcbiAgICBkbyB7XG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xuICAgIH0gd2hpbGUgKGNhbGxSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKlxuICogQ2FsbCBhIGJvdW5kIG1ldGhvZCBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIGNhbGwgb3B0aW9ucy5cbiAqXG4gKiBJbiBjYXNlIG9mIGZhaWx1cmUsIHRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgcmVqZWN0IHdpdGggYW4gZXhjZXB0aW9uXG4gKiBhbW9uZyBSZWZlcmVuY2VFcnJvciAodW5rbm93biBtZXRob2QpLCBUeXBlRXJyb3IgKHdyb25nIGFyZ3VtZW50IGNvdW50IG9yIHR5cGUpLFxuICoge0BsaW5rIFJ1bnRpbWVFcnJvcn0gKG1ldGhvZCByZXR1cm5lZCBhbiBlcnJvciksIG9yIG90aGVyIChuZXR3b3JrIG9yIGludGVybmFsIGVycm9ycykuXG4gKiBUaGUgZXhjZXB0aW9uIG1pZ2h0IGhhdmUgYSBcImNhdXNlXCIgZmllbGQgd2l0aCB0aGUgdmFsdWUgcmV0dXJuZWRcbiAqIGJ5IHRoZSBhcHBsaWNhdGlvbi0gb3Igc2VydmljZS1sZXZlbCBlcnJvciBtYXJzaGFsaW5nIGZ1bmN0aW9ucy5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIEEgbWV0aG9kIGNhbGwgZGVzY3JpcHRvci5cbiAqIEByZXR1cm5zIFRoZSByZXN1bHQgb2YgdGhlIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYWxsKG9wdGlvbnM6IENhbGxPcHRpb25zKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIGNvbnN0IGlkID0gZ2VuZXJhdGVJRCgpO1xuXG4gICAgY29uc3QgcmVzdWx0ID0gQ2FuY2VsbGFibGVQcm9taXNlLndpdGhSZXNvbHZlcnM8YW55PigpO1xuICAgIGNhbGxSZXNwb25zZXMuc2V0KGlkLCB7IHJlc29sdmU6IHJlc3VsdC5yZXNvbHZlLCByZWplY3Q6IHJlc3VsdC5yZWplY3QgfSk7XG5cbiAgICBjb25zdCByZXF1ZXN0ID0gY2FsbChDYWxsQmluZGluZywgT2JqZWN0LmFzc2lnbih7IFwiY2FsbC1pZFwiOiBpZCB9LCBvcHRpb25zKSk7XG4gICAgbGV0IHJ1bm5pbmcgPSBmYWxzZTtcblxuICAgIHJlcXVlc3QudGhlbigoKSA9PiB7XG4gICAgICAgIHJ1bm5pbmcgPSB0cnVlO1xuICAgIH0sIChlcnIpID0+IHtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICByZXN1bHQucmVqZWN0KGVycik7XG4gICAgfSk7XG5cbiAgICBjb25zdCBjYW5jZWwgPSAoKSA9PiB7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuZGVsZXRlKGlkKTtcbiAgICAgICAgcmV0dXJuIGNhbmNlbENhbGwoQ2FuY2VsTWV0aG9kLCB7XCJjYWxsLWlkXCI6IGlkfSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcIkVycm9yIHdoaWxlIHJlcXVlc3RpbmcgYmluZGluZyBjYWxsIGNhbmNlbGxhdGlvbjpcIiwgZXJyKTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHJlc3VsdC5vbmNhbmNlbGxlZCA9ICgpID0+IHtcbiAgICAgICAgaWYgKHJ1bm5pbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBjYW5jZWwoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiByZXF1ZXN0LnRoZW4oY2FuY2VsKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICByZXR1cm4gcmVzdWx0LnByb21pc2U7XG59XG5cbi8qKlxuICogQ2FsbHMgYSBib3VuZCBtZXRob2QgYnkgbmFtZSB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kTmFtZSAtIFRoZSBuYW1lIG9mIHRoZSBtZXRob2QgaW4gdGhlIGZvcm1hdCAncGFja2FnZS5zdHJ1Y3QubWV0aG9kJy5cbiAqIEBwYXJhbSBhcmdzIC0gVGhlIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2QuXG4gKiBAcmV0dXJucyBUaGUgcmVzdWx0IG9mIHRoZSBtZXRob2QgY2FsbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEJ5TmFtZShtZXRob2ROYW1lOiBzdHJpbmcsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kTmFtZSwgYXJncyB9KTtcbn1cblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBieSBpdHMgbnVtZXJpYyBJRCB3aXRoIHRoZSBzcGVjaWZpZWQgYXJndW1lbnRzLlxuICogU2VlIHtAbGluayBDYWxsfSBmb3IgZGV0YWlscy5cbiAqXG4gKiBAcGFyYW0gbWV0aG9kSUQgLSBUaGUgSUQgb2YgdGhlIG1ldGhvZCB0byBjYWxsLlxuICogQHBhcmFtIGFyZ3MgLSBUaGUgYXJndW1lbnRzIHRvIHBhc3MgdG8gdGhlIG1ldGhvZC5cbiAqIEByZXR1cm4gVGhlIHJlc3VsdCBvZiB0aGUgbWV0aG9kIGNhbGwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCeUlEKG1ldGhvZElEOiBudW1iZXIsIC4uLmFyZ3M6IGFueVtdKTogQ2FuY2VsbGFibGVQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiBDYWxsKHsgbWV0aG9kSUQsIGFyZ3MgfSk7XG59XG4iLCAiLy8gU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vaW5zcGVjdC1qcy9pcy1jYWxsYWJsZVxuXG4vLyBUaGUgTUlUIExpY2Vuc2UgKE1JVClcbi8vXG4vLyBDb3B5cmlnaHQgKGMpIDIwMTUgSm9yZGFuIEhhcmJhbmRcbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5XG4vLyBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsXG4vLyBpbiB0aGUgU29mdHdhcmUgd2l0aG91dCByZXN0cmljdGlvbiwgaW5jbHVkaW5nIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzXG4vLyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsXG4vLyBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXNcbi8vIGZ1cm5pc2hlZCB0byBkbyBzbywgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsXG4vLyBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLFxuLy8gRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFXG4vLyBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSXG4vLyBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLFxuLy8gT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEVcbi8vIFNPRlRXQVJFLlxuXG52YXIgZm5Ub1N0ciA9IEZ1bmN0aW9uLnByb3RvdHlwZS50b1N0cmluZztcbnZhciByZWZsZWN0QXBwbHk6IHR5cGVvZiBSZWZsZWN0LmFwcGx5IHwgZmFsc2UgfCBudWxsID0gdHlwZW9mIFJlZmxlY3QgPT09ICdvYmplY3QnICYmIFJlZmxlY3QgIT09IG51bGwgJiYgUmVmbGVjdC5hcHBseTtcbnZhciBiYWRBcnJheUxpa2U6IGFueTtcbnZhciBpc0NhbGxhYmxlTWFya2VyOiBhbnk7XG5pZiAodHlwZW9mIHJlZmxlY3RBcHBseSA9PT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2YgT2JqZWN0LmRlZmluZVByb3BlcnR5ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgYmFkQXJyYXlMaWtlID0gT2JqZWN0LmRlZmluZVByb3BlcnR5KHt9LCAnbGVuZ3RoJywge1xuICAgICAgICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgaXNDYWxsYWJsZU1hcmtlcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIGlzQ2FsbGFibGVNYXJrZXIgPSB7fTtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLXRocm93LWxpdGVyYWxcbiAgICAgICAgcmVmbGVjdEFwcGx5KGZ1bmN0aW9uICgpIHsgdGhyb3cgNDI7IH0sIG51bGwsIGJhZEFycmF5TGlrZSk7XG4gICAgfSBjYXRjaCAoXykge1xuICAgICAgICBpZiAoXyAhPT0gaXNDYWxsYWJsZU1hcmtlcikge1xuICAgICAgICAgICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbiAgICAgICAgfVxuICAgIH1cbn0gZWxzZSB7XG4gICAgcmVmbGVjdEFwcGx5ID0gbnVsbDtcbn1cblxudmFyIGNvbnN0cnVjdG9yUmVnZXggPSAvXlxccypjbGFzc1xcYi87XG52YXIgaXNFUzZDbGFzc0ZuID0gZnVuY3Rpb24gaXNFUzZDbGFzc0Z1bmN0aW9uKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICB2YXIgZm5TdHIgPSBmblRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICByZXR1cm4gY29uc3RydWN0b3JSZWdleC50ZXN0KGZuU3RyKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTsgLy8gbm90IGEgZnVuY3Rpb25cbiAgICB9XG59O1xuXG52YXIgdHJ5RnVuY3Rpb25PYmplY3QgPSBmdW5jdGlvbiB0cnlGdW5jdGlvblRvU3RyKHZhbHVlOiBhbnkpOiBib29sZWFuIHtcbiAgICB0cnkge1xuICAgICAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICAgICAgZm5Ub1N0ci5jYWxsKHZhbHVlKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufTtcbnZhciB0b1N0ciA9IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmc7XG52YXIgb2JqZWN0Q2xhc3MgPSAnW29iamVjdCBPYmplY3RdJztcbnZhciBmbkNsYXNzID0gJ1tvYmplY3QgRnVuY3Rpb25dJztcbnZhciBnZW5DbGFzcyA9ICdbb2JqZWN0IEdlbmVyYXRvckZ1bmN0aW9uXSc7XG52YXIgZGRhQ2xhc3MgPSAnW29iamVjdCBIVE1MQWxsQ29sbGVjdGlvbl0nOyAvLyBJRSAxMVxudmFyIGRkYUNsYXNzMiA9ICdbb2JqZWN0IEhUTUwgZG9jdW1lbnQuYWxsIGNsYXNzXSc7XG52YXIgZGRhQ2xhc3MzID0gJ1tvYmplY3QgSFRNTENvbGxlY3Rpb25dJzsgLy8gSUUgOS0xMFxudmFyIGhhc1RvU3RyaW5nVGFnID0gdHlwZW9mIFN5bWJvbCA9PT0gJ2Z1bmN0aW9uJyAmJiAhIVN5bWJvbC50b1N0cmluZ1RhZzsgLy8gYmV0dGVyOiB1c2UgYGhhcy10b3N0cmluZ3RhZ2BcblxudmFyIGlzSUU2OCA9ICEoMCBpbiBbLF0pOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLXNwYXJzZS1hcnJheXMsIGNvbW1hLXNwYWNpbmdcblxudmFyIGlzRERBOiAodmFsdWU6IGFueSkgPT4gYm9vbGVhbiA9IGZ1bmN0aW9uIGlzRG9jdW1lbnREb3RBbGwoKSB7IHJldHVybiBmYWxzZTsgfTtcbmlmICh0eXBlb2YgZG9jdW1lbnQgPT09ICdvYmplY3QnKSB7XG4gICAgLy8gRmlyZWZveCAzIGNhbm9uaWNhbGl6ZXMgRERBIHRvIHVuZGVmaW5lZCB3aGVuIGl0J3Mgbm90IGFjY2Vzc2VkIGRpcmVjdGx5XG4gICAgdmFyIGFsbCA9IGRvY3VtZW50LmFsbDtcbiAgICBpZiAodG9TdHIuY2FsbChhbGwpID09PSB0b1N0ci5jYWxsKGRvY3VtZW50LmFsbCkpIHtcbiAgICAgICAgaXNEREEgPSBmdW5jdGlvbiBpc0RvY3VtZW50RG90QWxsKHZhbHVlKSB7XG4gICAgICAgICAgICAvKiBnbG9iYWxzIGRvY3VtZW50OiBmYWxzZSAqL1xuICAgICAgICAgICAgLy8gaW4gSUUgNi04LCB0eXBlb2YgZG9jdW1lbnQuYWxsIGlzIFwib2JqZWN0XCIgYW5kIGl0J3MgdHJ1dGh5XG4gICAgICAgICAgICBpZiAoKGlzSUU2OCB8fCAhdmFsdWUpICYmICh0eXBlb2YgdmFsdWUgPT09ICd1bmRlZmluZWQnIHx8IHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpKSB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIHN0ciA9IHRvU3RyLmNhbGwodmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RyID09PSBkZGFDbGFzc1xuICAgICAgICAgICAgICAgICAgICAgICAgfHwgc3RyID09PSBkZGFDbGFzczJcbiAgICAgICAgICAgICAgICAgICAgICAgIHx8IHN0ciA9PT0gZGRhQ2xhc3MzIC8vIG9wZXJhIDEyLjE2XG4gICAgICAgICAgICAgICAgICAgICAgICB8fCBzdHIgPT09IG9iamVjdENsYXNzIC8vIElFIDYtOFxuICAgICAgICAgICAgICAgICAgICApICYmIHZhbHVlKCcnKSA9PSBudWxsOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGVxZXFlcVxuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsgLyoqLyB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBpc0NhbGxhYmxlUmVmQXBwbHk8VD4odmFsdWU6IFQgfCB1bmtub3duKTogdmFsdWUgaXMgKC4uLmFyZ3M6IGFueVtdKSA9PiBhbnkgIHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgdHJ5IHtcbiAgICAgICAgKHJlZmxlY3RBcHBseSBhcyBhbnkpKHZhbHVlLCBudWxsLCBiYWRBcnJheUxpa2UpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgaWYgKGUgIT09IGlzQ2FsbGFibGVNYXJrZXIpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgfVxuICAgIHJldHVybiAhaXNFUzZDbGFzc0ZuKHZhbHVlKSAmJiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59XG5cbmZ1bmN0aW9uIGlzQ2FsbGFibGVOb1JlZkFwcGx5PFQ+KHZhbHVlOiBUIHwgdW5rbm93bik6IHZhbHVlIGlzICguLi5hcmdzOiBhbnlbXSkgPT4gYW55IHtcbiAgICBpZiAoaXNEREEodmFsdWUpKSB7IHJldHVybiB0cnVlOyB9XG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnZnVuY3Rpb24nICYmIHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHsgcmV0dXJuIGZhbHNlOyB9XG4gICAgaWYgKGhhc1RvU3RyaW5nVGFnKSB7IHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7IH1cbiAgICBpZiAoaXNFUzZDbGFzc0ZuKHZhbHVlKSkgeyByZXR1cm4gZmFsc2U7IH1cbiAgICB2YXIgc3RyQ2xhc3MgPSB0b1N0ci5jYWxsKHZhbHVlKTtcbiAgICBpZiAoc3RyQ2xhc3MgIT09IGZuQ2xhc3MgJiYgc3RyQ2xhc3MgIT09IGdlbkNsYXNzICYmICEoL15cXFtvYmplY3QgSFRNTC8pLnRlc3Qoc3RyQ2xhc3MpKSB7IHJldHVybiBmYWxzZTsgfVxuICAgIHJldHVybiB0cnlGdW5jdGlvbk9iamVjdCh2YWx1ZSk7XG59O1xuXG5leHBvcnQgZGVmYXVsdCByZWZsZWN0QXBwbHkgPyBpc0NhbGxhYmxlUmVmQXBwbHkgOiBpc0NhbGxhYmxlTm9SZWZBcHBseTtcbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IGlzQ2FsbGFibGUgZnJvbSBcIi4vY2FsbGFibGUuanNcIjtcblxuLyoqXG4gKiBFeGNlcHRpb24gY2xhc3MgdGhhdCB3aWxsIGJlIHVzZWQgYXMgcmVqZWN0aW9uIHJlYXNvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IGlzIGNhbmNlbGxlZCBzdWNjZXNzZnVsbHkuXG4gKlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgbmFtZX0gcHJvcGVydHkgaXMgdGhlIHN0cmluZyBgXCJDYW5jZWxFcnJvclwiYC5cbiAqIFRoZSB2YWx1ZSBvZiB0aGUge0BsaW5rIGNhdXNlfSBwcm9wZXJ0eSBpcyB0aGUgY2F1c2UgcGFzc2VkIHRvIHRoZSBjYW5jZWwgbWV0aG9kLCBpZiBhbnkuXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxFcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIG1lc3NhZ2UgLSBUaGUgZXJyb3IgbWVzc2FnZS5cbiAgICAgKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgdG8gYmUgZm9yd2FyZGVkIHRvIHRoZSBFcnJvciBjb25zdHJ1Y3Rvci5cbiAgICAgKi9cbiAgICBjb25zdHJ1Y3RvcihtZXNzYWdlPzogc3RyaW5nLCBvcHRpb25zPzogRXJyb3JPcHRpb25zKSB7XG4gICAgICAgIHN1cGVyKG1lc3NhZ2UsIG9wdGlvbnMpO1xuICAgICAgICB0aGlzLm5hbWUgPSBcIkNhbmNlbEVycm9yXCI7XG4gICAgfVxufVxuXG4vKipcbiAqIEV4Y2VwdGlvbiBjbGFzcyB0aGF0IHdpbGwgYmUgcmVwb3J0ZWQgYXMgYW4gdW5oYW5kbGVkIHJlamVjdGlvblxuICogaW4gY2FzZSBhIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2V9IHJlamVjdHMgYWZ0ZXIgYmVpbmcgY2FuY2VsbGVkLFxuICogb3Igd2hlbiB0aGUgYG9uY2FuY2VsbGVkYCBjYWxsYmFjayB0aHJvd3Mgb3IgcmVqZWN0cy5cbiAqXG4gKiBUaGUgdmFsdWUgb2YgdGhlIHtAbGluayBuYW1lfSBwcm9wZXJ0eSBpcyB0aGUgc3RyaW5nIGBcIkNhbmNlbGxlZFJlamVjdGlvbkVycm9yXCJgLlxuICogVGhlIHZhbHVlIG9mIHRoZSB7QGxpbmsgY2F1c2V9IHByb3BlcnR5IGlzIHRoZSByZWFzb24gdGhlIHByb21pc2UgcmVqZWN0ZWQgd2l0aC5cbiAqXG4gKiBCZWNhdXNlIHRoZSBvcmlnaW5hbCBwcm9taXNlIHdhcyBjYW5jZWxsZWQsXG4gKiBhIHdyYXBwZXIgcHJvbWlzZSB3aWxsIGJlIHBhc3NlZCB0byB0aGUgdW5oYW5kbGVkIHJlamVjdGlvbiBsaXN0ZW5lciBpbnN0ZWFkLlxuICogVGhlIHtAbGluayBwcm9taXNlfSBwcm9wZXJ0eSBob2xkcyBhIHJlZmVyZW5jZSB0byB0aGUgb3JpZ2luYWwgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGNsYXNzIENhbmNlbGxlZFJlamVjdGlvbkVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAgIC8qKlxuICAgICAqIEhvbGRzIGEgcmVmZXJlbmNlIHRvIHRoZSBwcm9taXNlIHRoYXQgd2FzIGNhbmNlbGxlZCBhbmQgdGhlbiByZWplY3RlZC5cbiAgICAgKi9cbiAgICBwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj47XG5cbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIGEgbmV3IGBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcmAgaW5zdGFuY2UuXG4gICAgICogQHBhcmFtIHByb21pc2UgLSBUaGUgcHJvbWlzZSB0aGF0IGNhdXNlZCB0aGUgZXJyb3Igb3JpZ2luYWxseS5cbiAgICAgKiBAcGFyYW0gcmVhc29uIC0gVGhlIHJlamVjdGlvbiByZWFzb24uXG4gICAgICogQHBhcmFtIGluZm8gLSBBbiBvcHRpb25hbCBpbmZvcm1hdGl2ZSBtZXNzYWdlIHNwZWNpZnlpbmcgdGhlIGNpcmN1bXN0YW5jZXMgaW4gd2hpY2ggdGhlIGVycm9yIHdhcyB0aHJvd24uXG4gICAgICogICAgICAgICAgICAgICBEZWZhdWx0cyB0byB0aGUgc3RyaW5nIGBcIlVuaGFuZGxlZCByZWplY3Rpb24gaW4gY2FuY2VsbGVkIHByb21pc2UuXCJgLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiwgcmVhc29uPzogYW55LCBpbmZvPzogc3RyaW5nKSB7XG4gICAgICAgIHN1cGVyKChpbmZvID8/IFwiVW5oYW5kbGVkIHJlamVjdGlvbiBpbiBjYW5jZWxsZWQgcHJvbWlzZS5cIikgKyBcIiBSZWFzb246IFwiICsgZXJyb3JNZXNzYWdlKHJlYXNvbiksIHsgY2F1c2U6IHJlYXNvbiB9KTtcbiAgICAgICAgdGhpcy5wcm9taXNlID0gcHJvbWlzZTtcbiAgICAgICAgdGhpcy5uYW1lID0gXCJDYW5jZWxsZWRSZWplY3Rpb25FcnJvclwiO1xuICAgIH1cbn1cblxudHlwZSBDYW5jZWxsYWJsZVByb21pc2VSZXNvbHZlcjxUPiA9ICh2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IgPSAocmVhc29uPzogYW55KSA9PiB2b2lkO1xudHlwZSBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIgPSAoY2F1c2U/OiBhbnkpID0+IHZvaWQgfCBQcm9taXNlTGlrZTx2b2lkPjtcbnR5cGUgQ2FuY2VsbGFibGVQcm9taXNlRXhlY3V0b3I8VD4gPSAocmVzb2x2ZTogQ2FuY2VsbGFibGVQcm9taXNlUmVzb2x2ZXI8VD4sIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3IpID0+IHZvaWQ7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUPiB7XG4gICAgdGhlbjxUUmVzdWx0MSA9IFQsIFRSZXN1bHQyID0gbmV2ZXI+KG9uZnVsZmlsbGVkPzogKCh2YWx1ZTogVCkgPT4gVFJlc3VsdDEgfCBQcm9taXNlTGlrZTxUUmVzdWx0MT4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQxPikgfCB1bmRlZmluZWQgfCBudWxsLCBvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gVFJlc3VsdDIgfCBQcm9taXNlTGlrZTxUUmVzdWx0Mj4gfCBDYW5jZWxsYWJsZVByb21pc2VMaWtlPFRSZXN1bHQyPikgfCB1bmRlZmluZWQgfCBudWxsKTogQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MSB8IFRSZXN1bHQyPjtcbiAgICBjYW5jZWwoY2F1c2U/OiBhbnkpOiB2b2lkIHwgUHJvbWlzZUxpa2U8dm9pZD47XG59XG5cbi8qKlxuICogV3JhcHMgYSBjYW5jZWxsYWJsZSBwcm9taXNlIGFsb25nIHdpdGggaXRzIHJlc29sdXRpb24gbWV0aG9kcy5cbiAqIFRoZSBgb25jYW5jZWxsZWRgIGZpZWxkIHdpbGwgYmUgbnVsbCBpbml0aWFsbHkgYnV0IG1heSBiZSBzZXQgdG8gcHJvdmlkZSBhIGN1c3RvbSBjYW5jZWxsYXRpb24gZnVuY3Rpb24uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiB7XG4gICAgcHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+O1xuICAgIHJlc29sdmU6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+O1xuICAgIHJlamVjdDogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3I7XG4gICAgb25jYW5jZWxsZWQ6IENhbmNlbGxhYmxlUHJvbWlzZUNhbmNlbGxlciB8IG51bGw7XG59XG5cbmludGVyZmFjZSBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSB7XG4gICAgcmVhZG9ubHkgcm9vdDogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGU7XG4gICAgcmVzb2x2aW5nOiBib29sZWFuO1xuICAgIHNldHRsZWQ6IGJvb2xlYW47XG4gICAgcmVhc29uPzogQ2FuY2VsRXJyb3I7XG59XG5cbi8vIFByaXZhdGUgZmllbGQgbmFtZXMuXG5jb25zdCBiYXJyaWVyU3ltID0gU3ltYm9sKFwiYmFycmllclwiKTtcbmNvbnN0IGNhbmNlbEltcGxTeW0gPSBTeW1ib2woXCJjYW5jZWxJbXBsXCIpO1xuY29uc3Qgc3BlY2llcyA9IFN5bWJvbC5zcGVjaWVzID8/IFN5bWJvbChcInNwZWNpZXNQb2x5ZmlsbFwiKTtcblxuLyoqXG4gKiBBIHByb21pc2Ugd2l0aCBhbiBhdHRhY2hlZCBtZXRob2QgZm9yIGNhbmNlbGxpbmcgbG9uZy1ydW5uaW5nIG9wZXJhdGlvbnMgKHNlZSB7QGxpbmsgQ2FuY2VsbGFibGVQcm9taXNlI2NhbmNlbH0pLlxuICogQ2FuY2VsbGF0aW9uIGNhbiBvcHRpb25hbGx5IGJlIGJvdW5kIHRvIGFuIHtAbGluayBBYm9ydFNpZ25hbH1cbiAqIGZvciBiZXR0ZXIgY29tcG9zYWJpbGl0eSAoc2VlIHtAbGluayBDYW5jZWxsYWJsZVByb21pc2UjY2FuY2VsT259KS5cbiAqXG4gKiBDYW5jZWxsaW5nIGEgcGVuZGluZyBwcm9taXNlIHdpbGwgcmVzdWx0IGluIGFuIGltbWVkaWF0ZSByZWplY3Rpb25cbiAqIHdpdGggYW4gaW5zdGFuY2Ugb2Yge0BsaW5rIENhbmNlbEVycm9yfSBhcyByZWFzb24sXG4gKiBidXQgd2hvZXZlciBzdGFydGVkIHRoZSBwcm9taXNlIHdpbGwgYmUgcmVzcG9uc2libGVcbiAqIGZvciBhY3R1YWxseSBhYm9ydGluZyB0aGUgdW5kZXJseWluZyBvcGVyYXRpb24uXG4gKiBUbyB0aGlzIHB1cnBvc2UsIHRoZSBjb25zdHJ1Y3RvciBhbmQgYWxsIGNoYWluaW5nIG1ldGhvZHNcbiAqIGFjY2VwdCBvcHRpb25hbCBjYW5jZWxsYXRpb24gY2FsbGJhY2tzLlxuICpcbiAqIElmIGEgYENhbmNlbGxhYmxlUHJvbWlzZWAgc3RpbGwgcmVzb2x2ZXMgYWZ0ZXIgaGF2aW5nIGJlZW4gY2FuY2VsbGVkLFxuICogdGhlIHJlc3VsdCB3aWxsIGJlIGRpc2NhcmRlZC4gSWYgaXQgcmVqZWN0cywgdGhlIHJlYXNvblxuICogd2lsbCBiZSByZXBvcnRlZCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLFxuICogd3JhcHBlZCBpbiBhIHtAbGluayBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcn0gaW5zdGFuY2UuXG4gKiBUbyBmYWNpbGl0YXRlIHRoZSBoYW5kbGluZyBvZiBjYW5jZWxsYXRpb24gcmVxdWVzdHMsXG4gKiBjYW5jZWxsZWQgYENhbmNlbGxhYmxlUHJvbWlzZWBzIHdpbGwgX25vdF8gcmVwb3J0IHVuaGFuZGxlZCBgQ2FuY2VsRXJyb3Jgc1xuICogd2hvc2UgYGNhdXNlYCBmaWVsZCBpcyB0aGUgc2FtZSBhcyB0aGUgb25lIHdpdGggd2hpY2ggdGhlIGN1cnJlbnQgcHJvbWlzZSB3YXMgY2FuY2VsbGVkLlxuICpcbiAqIEFsbCB1c3VhbCBwcm9taXNlIG1ldGhvZHMgYXJlIGRlZmluZWQgYW5kIHJldHVybiBhIGBDYW5jZWxsYWJsZVByb21pc2VgXG4gKiB3aG9zZSBjYW5jZWwgbWV0aG9kIHdpbGwgY2FuY2VsIHRoZSBwYXJlbnQgb3BlcmF0aW9uIGFzIHdlbGwsIHByb3BhZ2F0aW5nIHRoZSBjYW5jZWxsYXRpb24gcmVhc29uXG4gKiB1cHdhcmRzIHRocm91Z2ggcHJvbWlzZSBjaGFpbnMuXG4gKiBDb252ZXJzZWx5LCBjYW5jZWxsaW5nIGEgcHJvbWlzZSB3aWxsIG5vdCBhdXRvbWF0aWNhbGx5IGNhbmNlbCBkZXBlbmRlbnQgcHJvbWlzZXMgZG93bnN0cmVhbTpcbiAqIGBgYHRzXG4gKiBsZXQgcm9vdCA9IG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4geyAuLi4gfSk7XG4gKiBsZXQgY2hpbGQxID0gcm9vdC50aGVuKCgpID0+IHsgLi4uIH0pO1xuICogbGV0IGNoaWxkMiA9IGNoaWxkMS50aGVuKCgpID0+IHsgLi4uIH0pO1xuICogbGV0IGNoaWxkMyA9IHJvb3QuY2F0Y2goKCkgPT4geyAuLi4gfSk7XG4gKiBjaGlsZDEuY2FuY2VsKCk7IC8vIENhbmNlbHMgY2hpbGQxIGFuZCByb290LCBidXQgbm90IGNoaWxkMiBvciBjaGlsZDNcbiAqIGBgYFxuICogQ2FuY2VsbGluZyBhIHByb21pc2UgdGhhdCBoYXMgYWxyZWFkeSBzZXR0bGVkIGlzIHNhZmUgYW5kIGhhcyBubyBjb25zZXF1ZW5jZS5cbiAqXG4gKiBUaGUgYGNhbmNlbGAgbWV0aG9kIHJldHVybnMgYSBwcm9taXNlIHRoYXQgX2Fsd2F5cyBmdWxmaWxsc19cbiAqIGFmdGVyIHRoZSB3aG9sZSBjaGFpbiBoYXMgcHJvY2Vzc2VkIHRoZSBjYW5jZWwgcmVxdWVzdFxuICogYW5kIGFsbCBhdHRhY2hlZCBjYWxsYmFja3MgdXAgdG8gdGhhdCBtb21lbnQgaGF2ZSBydW4uXG4gKlxuICogQWxsIEVTMjAyNCBwcm9taXNlIG1ldGhvZHMgKHN0YXRpYyBhbmQgaW5zdGFuY2UpIGFyZSBkZWZpbmVkIG9uIENhbmNlbGxhYmxlUHJvbWlzZSxcbiAqIGJ1dCBhY3R1YWwgYXZhaWxhYmlsaXR5IG1heSB2YXJ5IHdpdGggT1Mvd2VidmlldyB2ZXJzaW9uLlxuICpcbiAqIEluIGxpbmUgd2l0aCB0aGUgcHJvcG9zYWwgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RjMzkvcHJvcG9zYWwtcm0tYnVpbHRpbi1zdWJjbGFzc2luZyxcbiAqIGBDYW5jZWxsYWJsZVByb21pc2VgIGRvZXMgbm90IHN1cHBvcnQgdHJhbnNwYXJlbnQgc3ViY2xhc3NpbmcuXG4gKiBFeHRlbmRlcnMgc2hvdWxkIHRha2UgY2FyZSB0byBwcm92aWRlIHRoZWlyIG93biBtZXRob2QgaW1wbGVtZW50YXRpb25zLlxuICogVGhpcyBtaWdodCBiZSByZWNvbnNpZGVyZWQgaW4gY2FzZSB0aGUgcHJvcG9zYWwgaXMgcmV0aXJlZC5cbiAqXG4gKiBDYW5jZWxsYWJsZVByb21pc2UgaXMgYSB3cmFwcGVyIGFyb3VuZCB0aGUgRE9NIFByb21pc2Ugb2JqZWN0XG4gKiBhbmQgaXMgY29tcGxpYW50IHdpdGggdGhlIFtQcm9taXNlcy9BKyBzcGVjaWZpY2F0aW9uXShodHRwczovL3Byb21pc2VzYXBsdXMuY29tLylcbiAqIChpdCBwYXNzZXMgdGhlIFtjb21wbGlhbmNlIHN1aXRlXShodHRwczovL2dpdGh1Yi5jb20vcHJvbWlzZXMtYXBsdXMvcHJvbWlzZXMtdGVzdHMpKVxuICogaWYgc28gaXMgdGhlIHVuZGVybHlpbmcgaW1wbGVtZW50YXRpb24uXG4gKi9cbmV4cG9ydCBjbGFzcyBDYW5jZWxsYWJsZVByb21pc2U8VD4gZXh0ZW5kcyBQcm9taXNlPFQ+IGltcGxlbWVudHMgUHJvbWlzZUxpa2U8VD4sIENhbmNlbGxhYmxlUHJvbWlzZUxpa2U8VD4ge1xuICAgIC8vIFByaXZhdGUgZmllbGRzLlxuICAgIC8qKiBAaW50ZXJuYWwgKi9cbiAgICBwcml2YXRlIFtiYXJyaWVyU3ltXSE6IFBhcnRpYWw8UHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4+IHwgbnVsbDtcbiAgICAvKiogQGludGVybmFsICovXG4gICAgcHJpdmF0ZSByZWFkb25seSBbY2FuY2VsSW1wbFN5bV0hOiAocmVhc29uOiBDYW5jZWxFcnJvcikgPT4gdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+O1xuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBgQ2FuY2VsbGFibGVQcm9taXNlYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBleGVjdXRvciAtIEEgY2FsbGJhY2sgdXNlZCB0byBpbml0aWFsaXplIHRoZSBwcm9taXNlLiBUaGlzIGNhbGxiYWNrIGlzIHBhc3NlZCB0d28gYXJndW1lbnRzOlxuICAgICAqICAgICAgICAgICAgICAgICAgIGEgYHJlc29sdmVgIGNhbGxiYWNrIHVzZWQgdG8gcmVzb2x2ZSB0aGUgcHJvbWlzZSB3aXRoIGEgdmFsdWVcbiAgICAgKiAgICAgICAgICAgICAgICAgICBvciB0aGUgcmVzdWx0IG9mIGFub3RoZXIgcHJvbWlzZSAocG9zc2libHkgY2FuY2VsbGFibGUpLFxuICAgICAqICAgICAgICAgICAgICAgICAgIGFuZCBhIGByZWplY3RgIGNhbGxiYWNrIHVzZWQgdG8gcmVqZWN0IHRoZSBwcm9taXNlIHdpdGggYSBwcm92aWRlZCByZWFzb24gb3IgZXJyb3IuXG4gICAgICogICAgICAgICAgICAgICAgICAgSWYgdGhlIHZhbHVlIHByb3ZpZGVkIHRvIHRoZSBgcmVzb2x2ZWAgY2FsbGJhY2sgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiAgICAgICAgICAgICAgICAgICAoaXQgaGFzIGEgYHRoZW5gIF9hbmRfIGEgYGNhbmNlbGAgbWV0aG9kKSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBmb3J3YXJkZWQgdG8gdGhhdCBvYmplY3QgYW5kIHRoZSBvbmNhbmNlbGxlZCB3aWxsIG5vdCBiZSBpbnZva2VkIGFueW1vcmUuXG4gICAgICogICAgICAgICAgICAgICAgICAgSWYgYW55IG9uZSBvZiB0aGUgdHdvIGNhbGxiYWNrcyBpcyBjYWxsZWQgX2FmdGVyXyB0aGUgcHJvbWlzZSBoYXMgYmVlbiBjYW5jZWxsZWQsXG4gICAgICogICAgICAgICAgICAgICAgICAgdGhlIHByb3ZpZGVkIHZhbHVlcyB3aWxsIGJlIGNhbmNlbGxlZCBhbmQgcmVzb2x2ZWQgYXMgdXN1YWwsXG4gICAgICogICAgICAgICAgICAgICAgICAgYnV0IHRoZWlyIHJlc3VsdHMgd2lsbCBiZSBkaXNjYXJkZWQuXG4gICAgICogICAgICAgICAgICAgICAgICAgSG93ZXZlciwgaWYgdGhlIHJlc29sdXRpb24gcHJvY2VzcyB1bHRpbWF0ZWx5IGVuZHMgdXAgaW4gYSByZWplY3Rpb25cbiAgICAgKiAgICAgICAgICAgICAgICAgICB0aGF0IGlzIG5vdCBkdWUgdG8gY2FuY2VsbGF0aW9uLCB0aGUgcmVqZWN0aW9uIHJlYXNvblxuICAgICAqICAgICAgICAgICAgICAgICAgIHdpbGwgYmUgd3JhcHBlZCBpbiBhIHtAbGluayBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcn1cbiAgICAgKiAgICAgICAgICAgICAgICAgICBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqIEBwYXJhbSBvbmNhbmNlbGxlZCAtIEl0IGlzIHRoZSBjYWxsZXIncyByZXNwb25zaWJpbGl0eSB0byBlbnN1cmUgdGhhdCBhbnkgb3BlcmF0aW9uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgc3RhcnRlZCBieSB0aGUgZXhlY3V0b3IgaXMgcHJvcGVybHkgaGFsdGVkIHVwb24gY2FuY2VsbGF0aW9uLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIFRoaXMgb3B0aW9uYWwgY2FsbGJhY2sgY2FuIGJlIHVzZWQgdG8gdGhhdCBwdXJwb3NlLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIEl0IHdpbGwgYmUgY2FsbGVkIF9zeW5jaHJvbm91c2x5XyB3aXRoIGEgY2FuY2VsbGF0aW9uIGNhdXNlXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2hlbiBjYW5jZWxsYXRpb24gaXMgcmVxdWVzdGVkLCBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBhbHJlYWR5IHJlamVjdGVkXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2l0aCBhIHtAbGluayBDYW5jZWxFcnJvcn0sIGJ1dCBfYmVmb3JlX1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGFueSB7QGxpbmsgdGhlbn0ve0BsaW5rIGNhdGNofS97QGxpbmsgZmluYWxseX0gY2FsbGJhY2sgcnVucy5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBJZiB0aGUgY2FsbGJhY2sgcmV0dXJucyBhIHRoZW5hYmxlLCB0aGUgcHJvbWlzZSByZXR1cm5lZCBmcm9tIHtAbGluayBjYW5jZWx9XG4gICAgICogICAgICAgICAgICAgICAgICAgICAgd2lsbCBvbmx5IGZ1bGZpbGwgYWZ0ZXIgdGhlIGZvcm1lciBoYXMgc2V0dGxlZC5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBVbmhhbmRsZWQgZXhjZXB0aW9ucyBvciByZWplY3Rpb25zIGZyb20gdGhlIGNhbGxiYWNrIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyB1bmhhbmRsZWQgcmVqZWN0aW9ucy5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICBJZiB0aGUgYHJlc29sdmVgIGNhbGxiYWNrIGlzIGNhbGxlZCBiZWZvcmUgY2FuY2VsbGF0aW9uIHdpdGggYSBjYW5jZWxsYWJsZSBwcm9taXNlLFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgIGNhbmNlbGxhdGlvbiByZXF1ZXN0cyBvbiB0aGlzIHByb21pc2Ugd2lsbCBiZSBkaXZlcnRlZCB0byB0aGF0IHByb21pc2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgYW5kIHRoZSBvcmlnaW5hbCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV4ZWN1dG9yOiBDYW5jZWxsYWJsZVByb21pc2VFeGVjdXRvcjxUPiwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpIHtcbiAgICAgICAgbGV0IHJlc29sdmUhOiAodmFsdWU6IFQgfCBQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbiAgICAgICAgbGV0IHJlamVjdCE6IChyZWFzb24/OiBhbnkpID0+IHZvaWQ7XG4gICAgICAgIHN1cGVyKChyZXMsIHJlaikgPT4geyByZXNvbHZlID0gcmVzOyByZWplY3QgPSByZWo7IH0pO1xuXG4gICAgICAgIGlmICgodGhpcy5jb25zdHJ1Y3RvciBhcyBhbnkpW3NwZWNpZXNdICE9PSBQcm9taXNlKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2FuY2VsbGFibGVQcm9taXNlIGRvZXMgbm90IHN1cHBvcnQgdHJhbnNwYXJlbnQgc3ViY2xhc3NpbmcuIFBsZWFzZSByZWZyYWluIGZyb20gb3ZlcnJpZGluZyB0aGUgW1N5bWJvbC5zcGVjaWVzXSBzdGF0aWMgcHJvcGVydHkuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgbGV0IHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4gPSB7XG4gICAgICAgICAgICBwcm9taXNlOiB0aGlzLFxuICAgICAgICAgICAgcmVzb2x2ZSxcbiAgICAgICAgICAgIHJlamVjdCxcbiAgICAgICAgICAgIGdldCBvbmNhbmNlbGxlZCgpIHsgcmV0dXJuIG9uY2FuY2VsbGVkID8/IG51bGw7IH0sXG4gICAgICAgICAgICBzZXQgb25jYW5jZWxsZWQoY2IpIHsgb25jYW5jZWxsZWQgPSBjYiA/PyB1bmRlZmluZWQ7IH1cbiAgICAgICAgfTtcblxuICAgICAgICBjb25zdCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUgPSB7XG4gICAgICAgICAgICBnZXQgcm9vdCgpIHsgcmV0dXJuIHN0YXRlOyB9LFxuICAgICAgICAgICAgcmVzb2x2aW5nOiBmYWxzZSxcbiAgICAgICAgICAgIHNldHRsZWQ6IGZhbHNlXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gU2V0dXAgY2FuY2VsbGF0aW9uIHN5c3RlbS5cbiAgICAgICAgdm9pZCBPYmplY3QuZGVmaW5lUHJvcGVydGllcyh0aGlzLCB7XG4gICAgICAgICAgICBbYmFycmllclN5bV06IHtcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBudWxsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgW2NhbmNlbEltcGxTeW1dOiB7XG4gICAgICAgICAgICAgICAgY29uZmlndXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgdmFsdWU6IGNhbmNlbGxlckZvcihwcm9taXNlLCBzdGF0ZSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gUnVuIHRoZSBhY3R1YWwgZXhlY3V0b3IuXG4gICAgICAgIGNvbnN0IHJlamVjdG9yID0gcmVqZWN0b3JGb3IocHJvbWlzZSwgc3RhdGUpO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgZXhlY3V0b3IocmVzb2x2ZXJGb3IocHJvbWlzZSwgc3RhdGUpLCByZWplY3Rvcik7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgaWYgKHN0YXRlLnJlc29sdmluZykge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiVW5oYW5kbGVkIGV4Y2VwdGlvbiBpbiBDYW5jZWxsYWJsZVByb21pc2UgZXhlY3V0b3IuXCIsIGVycik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlamVjdG9yKGVycik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDYW5jZWxzIGltbWVkaWF0ZWx5IHRoZSBleGVjdXRpb24gb2YgdGhlIG9wZXJhdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhpcyBwcm9taXNlLlxuICAgICAqIFRoZSBwcm9taXNlIHJlamVjdHMgd2l0aCBhIHtAbGluayBDYW5jZWxFcnJvcn0gaW5zdGFuY2UgYXMgcmVhc29uLFxuICAgICAqIHdpdGggdGhlIHtAbGluayBDYW5jZWxFcnJvciNjYXVzZX0gcHJvcGVydHkgc2V0IHRvIHRoZSBnaXZlbiBhcmd1bWVudCwgaWYgYW55LlxuICAgICAqXG4gICAgICogSGFzIG5vIGVmZmVjdCBpZiBjYWxsZWQgYWZ0ZXIgdGhlIHByb21pc2UgaGFzIGFscmVhZHkgc2V0dGxlZDtcbiAgICAgKiByZXBlYXRlZCBjYWxscyBpbiBwYXJ0aWN1bGFyIGFyZSBzYWZlLCBidXQgb25seSB0aGUgZmlyc3Qgb25lXG4gICAgICogd2lsbCBzZXQgdGhlIGNhbmNlbGxhdGlvbiBjYXVzZS5cbiAgICAgKlxuICAgICAqIFRoZSBgQ2FuY2VsRXJyb3JgIGV4Y2VwdGlvbiBfbmVlZCBub3RfIGJlIGhhbmRsZWQgZXhwbGljaXRseSBfb24gdGhlIHByb21pc2VzIHRoYXQgYXJlIGJlaW5nIGNhbmNlbGxlZDpfXG4gICAgICogY2FuY2VsbGluZyBhIHByb21pc2Ugd2l0aCBubyBhdHRhY2hlZCByZWplY3Rpb24gaGFuZGxlciBkb2VzIG5vdCB0cmlnZ2VyIGFuIHVuaGFuZGxlZCByZWplY3Rpb24gZXZlbnQuXG4gICAgICogVGhlcmVmb3JlLCB0aGUgZm9sbG93aW5nIGlkaW9tcyBhcmUgYWxsIGVxdWFsbHkgY29ycmVjdDpcbiAgICAgKiBgYGB0c1xuICAgICAqIG5ldyBDYW5jZWxsYWJsZVByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4geyAuLi4gfSkuY2FuY2VsKCk7XG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS50aGVuKC4uLikuY2FuY2VsKCk7XG4gICAgICogbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KS50aGVuKC4uLikuY2F0Y2goLi4uKS5jYW5jZWwoKTtcbiAgICAgKiBgYGBcbiAgICAgKiBXaGVuZXZlciBzb21lIGNhbmNlbGxlZCBwcm9taXNlIGluIGEgY2hhaW4gcmVqZWN0cyB3aXRoIGEgYENhbmNlbEVycm9yYFxuICAgICAqIHdpdGggdGhlIHNhbWUgY2FuY2VsbGF0aW9uIGNhdXNlIGFzIGl0c2VsZiwgdGhlIGVycm9yIHdpbGwgYmUgZGlzY2FyZGVkIHNpbGVudGx5LlxuICAgICAqIEhvd2V2ZXIsIHRoZSBgQ2FuY2VsRXJyb3JgIF93aWxsIHN0aWxsIGJlIGRlbGl2ZXJlZF8gdG8gYWxsIGF0dGFjaGVkIHJlamVjdGlvbiBoYW5kbGVyc1xuICAgICAqIGFkZGVkIGJ5IHtAbGluayB0aGVufSBhbmQgcmVsYXRlZCBtZXRob2RzOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IGNhbmNlbGxhYmxlID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAgICAgKiBjYW5jZWxsYWJsZS50aGVuKCgpID0+IHsgLi4uIH0pLmNhdGNoKGNvbnNvbGUubG9nKTtcbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKTsgLy8gQSBDYW5jZWxFcnJvciBpcyBwcmludGVkIHRvIHRoZSBjb25zb2xlLlxuICAgICAqIGBgYFxuICAgICAqIElmIHRoZSBgQ2FuY2VsRXJyb3JgIGlzIG5vdCBoYW5kbGVkIGRvd25zdHJlYW0gYnkgdGhlIHRpbWUgaXQgcmVhY2hlc1xuICAgICAqIGEgX25vbi1jYW5jZWxsZWRfIHByb21pc2UsIGl0IF93aWxsXyB0cmlnZ2VyIGFuIHVuaGFuZGxlZCByZWplY3Rpb24gZXZlbnQsXG4gICAgICoganVzdCBsaWtlIG5vcm1hbCByZWplY3Rpb25zIHdvdWxkOlxuICAgICAqIGBgYHRzXG4gICAgICogbGV0IGNhbmNlbGxhYmxlID0gbmV3IENhbmNlbGxhYmxlUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7IC4uLiB9KTtcbiAgICAgKiBsZXQgY2hhaW5lZCA9IGNhbmNlbGxhYmxlLnRoZW4oKCkgPT4geyAuLi4gfSkudGhlbigoKSA9PiB7IC4uLiB9KTsgLy8gTm8gY2F0Y2guLi5cbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKTsgLy8gVW5oYW5kbGVkIHJlamVjdGlvbiBldmVudCBvbiBjaGFpbmVkIVxuICAgICAqIGBgYFxuICAgICAqIFRoZXJlZm9yZSwgaXQgaXMgaW1wb3J0YW50IHRvIGVpdGhlciBjYW5jZWwgd2hvbGUgcHJvbWlzZSBjaGFpbnMgZnJvbSB0aGVpciB0YWlsLFxuICAgICAqIGFzIHNob3duIGluIHRoZSBjb3JyZWN0IGlkaW9tcyBhYm92ZSwgb3IgdGFrZSBjYXJlIG9mIGhhbmRsaW5nIGVycm9ycyBldmVyeXdoZXJlLlxuICAgICAqXG4gICAgICogQHJldHVybnMgQSBjYW5jZWxsYWJsZSBwcm9taXNlIHRoYXQgX2Z1bGZpbGxzXyBhZnRlciB0aGUgY2FuY2VsIGNhbGxiYWNrIChpZiBhbnkpXG4gICAgICogYW5kIGFsbCBoYW5kbGVycyBhdHRhY2hlZCB1cCB0byB0aGUgY2FsbCB0byBjYW5jZWwgaGF2ZSBydW4uXG4gICAgICogSWYgdGhlIGNhbmNlbCBjYWxsYmFjayByZXR1cm5zIGEgdGhlbmFibGUsIHRoZSBwcm9taXNlIHJldHVybmVkIGJ5IGBjYW5jZWxgXG4gICAgICogd2lsbCBhbHNvIHdhaXQgZm9yIHRoYXQgdGhlbmFibGUgdG8gc2V0dGxlLlxuICAgICAqIFRoaXMgZW5hYmxlcyBjYWxsZXJzIHRvIHdhaXQgZm9yIHRoZSBjYW5jZWxsZWQgb3BlcmF0aW9uIHRvIHRlcm1pbmF0ZVxuICAgICAqIHdpdGhvdXQgYmVpbmcgZm9yY2VkIHRvIGhhbmRsZSBwb3RlbnRpYWwgZXJyb3JzIGF0IHRoZSBjYWxsIHNpdGUuXG4gICAgICogYGBgdHNcbiAgICAgKiBjYW5jZWxsYWJsZS5jYW5jZWwoKS50aGVuKCgpID0+IHtcbiAgICAgKiAgICAgLy8gQ2xlYW51cCBmaW5pc2hlZCwgaXQncyBzYWZlIHRvIGRvIHNvbWV0aGluZyBlbHNlLlxuICAgICAqIH0sIChlcnIpID0+IHtcbiAgICAgKiAgICAgLy8gVW5yZWFjaGFibGU6IHRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gY2FuY2VsIHdpbGwgbmV2ZXIgcmVqZWN0LlxuICAgICAqIH0pO1xuICAgICAqIGBgYFxuICAgICAqIE5vdGUgdGhhdCB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIF9ub3RfIGhhbmRsZSBpbXBsaWNpdGx5IGFueSByZWplY3Rpb25cbiAgICAgKiB0aGF0IG1pZ2h0IGhhdmUgb2NjdXJyZWQgYWxyZWFkeSBpbiB0aGUgY2FuY2VsbGVkIGNoYWluLlxuICAgICAqIEl0IHdpbGwganVzdCB0cmFjayB3aGV0aGVyIHJlZ2lzdGVyZWQgaGFuZGxlcnMgaGF2ZSBiZWVuIGV4ZWN1dGVkIG9yIG5vdC5cbiAgICAgKiBUaGVyZWZvcmUsIHVuaGFuZGxlZCByZWplY3Rpb25zIHdpbGwgbmV2ZXIgYmUgc2lsZW50bHkgaGFuZGxlZCBieSBjYWxsaW5nIGNhbmNlbC5cbiAgICAgKi9cbiAgICBjYW5jZWwoY2F1c2U/OiBhbnkpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgICAgLy8gSU5WQVJJQU5UOiB0aGUgcmVzdWx0IG9mIHRoaXNbY2FuY2VsSW1wbFN5bV0gYW5kIHRoZSBiYXJyaWVyIGRvIG5vdCBldmVyIHJlamVjdC5cbiAgICAgICAgICAgIC8vIFVuZm9ydHVuYXRlbHkgbWFjT1MgSGlnaCBTaWVycmEgZG9lcyBub3Qgc3VwcG9ydCBQcm9taXNlLmFsbFNldHRsZWQuXG4gICAgICAgICAgICBQcm9taXNlLmFsbChbXG4gICAgICAgICAgICAgICAgdGhpc1tjYW5jZWxJbXBsU3ltXShuZXcgQ2FuY2VsRXJyb3IoXCJQcm9taXNlIGNhbmNlbGxlZC5cIiwgeyBjYXVzZSB9KSksXG4gICAgICAgICAgICAgICAgY3VycmVudEJhcnJpZXIodGhpcylcbiAgICAgICAgICAgIF0pLnRoZW4oKCkgPT4gcmVzb2x2ZSgpLCAoKSA9PiByZXNvbHZlKCkpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBCaW5kcyBwcm9taXNlIGNhbmNlbGxhdGlvbiB0byB0aGUgYWJvcnQgZXZlbnQgb2YgdGhlIGdpdmVuIHtAbGluayBBYm9ydFNpZ25hbH0uXG4gICAgICogSWYgdGhlIHNpZ25hbCBoYXMgYWxyZWFkeSBhYm9ydGVkLCB0aGUgcHJvbWlzZSB3aWxsIGJlIGNhbmNlbGxlZCBpbW1lZGlhdGVseS5cbiAgICAgKiBXaGVuIGVpdGhlciBjb25kaXRpb24gaXMgdmVyaWZpZWQsIHRoZSBjYW5jZWxsYXRpb24gY2F1c2Ugd2lsbCBiZSBzZXRcbiAgICAgKiB0byB0aGUgc2lnbmFsJ3MgYWJvcnQgcmVhc29uIChzZWUge0BsaW5rIEFib3J0U2lnbmFsI3JlYXNvbn0pLlxuICAgICAqXG4gICAgICogSGFzIG5vIGVmZmVjdCBpZiBjYWxsZWQgKG9yIGlmIHRoZSBzaWduYWwgYWJvcnRzKSBfYWZ0ZXJfIHRoZSBwcm9taXNlIGhhcyBhbHJlYWR5IHNldHRsZWQuXG4gICAgICogT25seSB0aGUgZmlyc3Qgc2lnbmFsIHRvIGFib3J0IHdpbGwgc2V0IHRoZSBjYW5jZWxsYXRpb24gY2F1c2UuXG4gICAgICpcbiAgICAgKiBGb3IgbW9yZSBkZXRhaWxzIGFib3V0IHRoZSBjYW5jZWxsYXRpb24gcHJvY2VzcyxcbiAgICAgKiBzZWUge0BsaW5rIGNhbmNlbH0gYW5kIHRoZSBgQ2FuY2VsbGFibGVQcm9taXNlYCBjb25zdHJ1Y3Rvci5cbiAgICAgKlxuICAgICAqIFRoaXMgbWV0aG9kIGVuYWJsZXMgYGF3YWl0YGluZyBjYW5jZWxsYWJsZSBwcm9taXNlcyB3aXRob3V0IGhhdmluZ1xuICAgICAqIHRvIHN0b3JlIHRoZW0gZm9yIGZ1dHVyZSBjYW5jZWxsYXRpb24sIGUuZy46XG4gICAgICogYGBgdHNcbiAgICAgKiBhd2FpdCBsb25nUnVubmluZ09wZXJhdGlvbigpLmNhbmNlbE9uKHNpZ25hbCk7XG4gICAgICogYGBgXG4gICAgICogaW5zdGVhZCBvZjpcbiAgICAgKiBgYGB0c1xuICAgICAqIGxldCBwcm9taXNlVG9CZUNhbmNlbGxlZCA9IGxvbmdSdW5uaW5nT3BlcmF0aW9uKCk7XG4gICAgICogYXdhaXQgcHJvbWlzZVRvQmVDYW5jZWxsZWQ7XG4gICAgICogYGBgXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGlzIHByb21pc2UsIGZvciBtZXRob2QgY2hhaW5pbmcuXG4gICAgICovXG4gICAgY2FuY2VsT24oc2lnbmFsOiBBYm9ydFNpZ25hbCk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGlmIChzaWduYWwuYWJvcnRlZCkge1xuICAgICAgICAgICAgdm9pZCB0aGlzLmNhbmNlbChzaWduYWwucmVhc29uKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2lnbmFsLmFkZEV2ZW50TGlzdGVuZXIoJ2Fib3J0JywgKCkgPT4gdm9pZCB0aGlzLmNhbmNlbChzaWduYWwucmVhc29uKSwge2NhcHR1cmU6IHRydWV9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEF0dGFjaGVzIGNhbGxiYWNrcyBmb3IgdGhlIHJlc29sdXRpb24gYW5kL29yIHJlamVjdGlvbiBvZiB0aGUgYENhbmNlbGxhYmxlUHJvbWlzZWAuXG4gICAgICpcbiAgICAgKiBUaGUgb3B0aW9uYWwgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCB3aWxsIGJlIGludm9rZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsXG4gICAgICogd2l0aCB0aGUgc2FtZSBzZW1hbnRpY3MgYXMgdGhlIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgb2YgdGhlIGNvbnN0cnVjdG9yLlxuICAgICAqIFdoZW4gdGhlIHBhcmVudCBwcm9taXNlIHJlamVjdHMgb3IgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogQHBhcmFtIG9uZnVsZmlsbGVkIFRoZSBjYWxsYmFjayB0byBleGVjdXRlIHdoZW4gdGhlIFByb21pc2UgaXMgcmVzb2x2ZWQuXG4gICAgICogQHBhcmFtIG9ucmVqZWN0ZWQgVGhlIGNhbGxiYWNrIHRvIGV4ZWN1dGUgd2hlbiB0aGUgUHJvbWlzZSBpcyByZWplY3RlZC5cbiAgICAgKiBAcmV0dXJucyBBIGBDYW5jZWxsYWJsZVByb21pc2VgIGZvciB0aGUgY29tcGxldGlvbiBvZiB3aGljaGV2ZXIgY2FsbGJhY2sgaXMgZXhlY3V0ZWQuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgaXMgaG9va2VkIHVwIHRvIHByb3BhZ2F0ZSBjYW5jZWxsYXRpb24gcmVxdWVzdHMgdXAgdGhlIGNoYWluLCBidXQgbm90IGRvd246XG4gICAgICpcbiAgICAgKiAgIC0gaWYgdGhlIHBhcmVudCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgdGhlIGBvbnJlamVjdGVkYCBoYW5kbGVyIHdpbGwgYmUgaW52b2tlZCB3aXRoIGEgYENhbmNlbEVycm9yYFxuICAgICAqICAgICBhbmQgdGhlIHJldHVybmVkIHByb21pc2UgX3dpbGwgcmVzb2x2ZSByZWd1bGFybHlfIHdpdGggaXRzIHJlc3VsdDtcbiAgICAgKiAgIC0gY29udmVyc2VseSwgaWYgdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCBfdGhlIHBhcmVudCBwcm9taXNlIGlzIGNhbmNlbGxlZCB0b287X1xuICAgICAqICAgICB0aGUgYG9ucmVqZWN0ZWRgIGhhbmRsZXIgd2lsbCBzdGlsbCBiZSBpbnZva2VkIHdpdGggdGhlIHBhcmVudCdzIGBDYW5jZWxFcnJvcmAsXG4gICAgICogICAgIGJ1dCBpdHMgcmVzdWx0IHdpbGwgYmUgZGlzY2FyZGVkXG4gICAgICogICAgIGFuZCB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIHJlamVjdCB3aXRoIGEgYENhbmNlbEVycm9yYCBhcyB3ZWxsLlxuICAgICAqXG4gICAgICogVGhlIHByb21pc2UgcmV0dXJuZWQgZnJvbSB7QGxpbmsgY2FuY2VsfSB3aWxsIGZ1bGZpbGwgb25seSBhZnRlciBhbGwgYXR0YWNoZWQgaGFuZGxlcnNcbiAgICAgKiB1cCB0aGUgZW50aXJlIHByb21pc2UgY2hhaW4gaGF2ZSBiZWVuIHJ1bi5cbiAgICAgKlxuICAgICAqIElmIGVpdGhlciBjYWxsYmFjayByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqL1xuICAgIHRoZW48VFJlc3VsdDEgPSBULCBUUmVzdWx0MiA9IG5ldmVyPihvbmZ1bGZpbGxlZD86ICgodmFsdWU6IFQpID0+IFRSZXN1bHQxIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDE+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0MT4pIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25yZWplY3RlZD86ICgocmVhc29uOiBhbnkpID0+IFRSZXN1bHQyIHwgUHJvbWlzZUxpa2U8VFJlc3VsdDI+IHwgQ2FuY2VsbGFibGVQcm9taXNlTGlrZTxUUmVzdWx0Mj4pIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VFJlc3VsdDEgfCBUUmVzdWx0Mj4ge1xuICAgICAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgQ2FuY2VsbGFibGVQcm9taXNlKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbmNlbGxhYmxlUHJvbWlzZS5wcm90b3R5cGUudGhlbiBjYWxsZWQgb24gYW4gaW52YWxpZCBvYmplY3QuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gTk9URTogVHlwZVNjcmlwdCdzIGJ1aWx0LWluIHR5cGUgZm9yIHRoZW4gaXMgYnJva2VuLFxuICAgICAgICAvLyBhcyBpdCBhbGxvd3Mgc3BlY2lmeWluZyBhbiBhcmJpdHJhcnkgVFJlc3VsdDEgIT0gVCBldmVuIHdoZW4gb25mdWxmaWxsZWQgaXMgbm90IGEgZnVuY3Rpb24uXG4gICAgICAgIC8vIFdlIGNhbm5vdCBmaXggaXQgaWYgd2Ugd2FudCB0byBDYW5jZWxsYWJsZVByb21pc2UgdG8gaW1wbGVtZW50IFByb21pc2VMaWtlPFQ+LlxuXG4gICAgICAgIGlmICghaXNDYWxsYWJsZShvbmZ1bGZpbGxlZCkpIHsgb25mdWxmaWxsZWQgPSBpZGVudGl0eSBhcyBhbnk7IH1cbiAgICAgICAgaWYgKCFpc0NhbGxhYmxlKG9ucmVqZWN0ZWQpKSB7IG9ucmVqZWN0ZWQgPSB0aHJvd2VyOyB9XG5cbiAgICAgICAgaWYgKG9uZnVsZmlsbGVkID09PSBpZGVudGl0eSAmJiBvbnJlamVjdGVkID09IHRocm93ZXIpIHtcbiAgICAgICAgICAgIC8vIFNob3J0Y3V0IGZvciB0cml2aWFsIGFyZ3VtZW50cy5cbiAgICAgICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlKChyZXNvbHZlKSA9PiByZXNvbHZlKHRoaXMgYXMgYW55KSk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBiYXJyaWVyOiBQYXJ0aWFsPFByb21pc2VXaXRoUmVzb2x2ZXJzPHZvaWQ+PiA9IHt9O1xuICAgICAgICB0aGlzW2JhcnJpZXJTeW1dID0gYmFycmllcjtcblxuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUUmVzdWx0MSB8IFRSZXN1bHQyPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICB2b2lkIHN1cGVyLnRoZW4oXG4gICAgICAgICAgICAgICAgKHZhbHVlKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzW2JhcnJpZXJTeW1dID09PSBiYXJyaWVyKSB7IHRoaXNbYmFycmllclN5bV0gPSBudWxsOyB9XG4gICAgICAgICAgICAgICAgICAgIGJhcnJpZXIucmVzb2x2ZT8uKCk7XG5cbiAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdmUob25mdWxmaWxsZWQhKHZhbHVlKSk7XG4gICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIChyZWFzb24/KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzW2JhcnJpZXJTeW1dID09PSBiYXJyaWVyKSB7IHRoaXNbYmFycmllclN5bV0gPSBudWxsOyB9XG4gICAgICAgICAgICAgICAgICAgIGJhcnJpZXIucmVzb2x2ZT8uKCk7XG5cbiAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdmUob25yZWplY3RlZCEocmVhc29uKSk7XG4gICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICApO1xuICAgICAgICB9LCBhc3luYyAoY2F1c2U/KSA9PiB7XG4gICAgICAgICAgICAvL2NhbmNlbGxlZCA9IHRydWU7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJldHVybiBvbmNhbmNlbGxlZD8uKGNhdXNlKTtcbiAgICAgICAgICAgIH0gZmluYWxseSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5jYW5jZWwoY2F1c2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRhY2hlcyBhIGNhbGxiYWNrIGZvciBvbmx5IHRoZSByZWplY3Rpb24gb2YgdGhlIFByb21pc2UuXG4gICAgICpcbiAgICAgKiBUaGUgb3B0aW9uYWwgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCB3aWxsIGJlIGludm9rZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsXG4gICAgICogd2l0aCB0aGUgc2FtZSBzZW1hbnRpY3MgYXMgdGhlIGBvbmNhbmNlbGxlZGAgYXJndW1lbnQgb2YgdGhlIGNvbnN0cnVjdG9yLlxuICAgICAqIFdoZW4gdGhlIHBhcmVudCBwcm9taXNlIHJlamVjdHMgb3IgaXMgY2FuY2VsbGVkLCB0aGUgYG9ucmVqZWN0ZWRgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogSXQgaXMgZXF1aXZhbGVudCB0b1xuICAgICAqIGBgYHRzXG4gICAgICogY2FuY2VsbGFibGVQcm9taXNlLnRoZW4odW5kZWZpbmVkLCBvbnJlamVjdGVkLCBvbmNhbmNlbGxlZCk7XG4gICAgICogYGBgXG4gICAgICogYW5kIHRoZSBzYW1lIGNhdmVhdHMgYXBwbHkuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIFByb21pc2UgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHRoZSBjYWxsYmFjay5cbiAgICAgKiBDYW5jZWxsYXRpb24gcmVxdWVzdHMgb24gdGhlIHJldHVybmVkIHByb21pc2VcbiAgICAgKiB3aWxsIHByb3BhZ2F0ZSB1cCB0aGUgY2hhaW4gdG8gdGhlIHBhcmVudCBwcm9taXNlLFxuICAgICAqIGJ1dCBub3QgaW4gdGhlIG90aGVyIGRpcmVjdGlvbi5cbiAgICAgKlxuICAgICAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH0gd2lsbCBmdWxmaWxsIG9ubHkgYWZ0ZXIgYWxsIGF0dGFjaGVkIGhhbmRsZXJzXG4gICAgICogdXAgdGhlIGVudGlyZSBwcm9taXNlIGNoYWluIGhhdmUgYmVlbiBydW4uXG4gICAgICpcbiAgICAgKiBJZiBgb25yZWplY3RlZGAgcmV0dXJucyBhIGNhbmNlbGxhYmxlIHByb21pc2UsXG4gICAgICogY2FuY2VsbGF0aW9uIHJlcXVlc3RzIHdpbGwgYmUgZGl2ZXJ0ZWQgdG8gaXQsXG4gICAgICogYW5kIHRoZSBzcGVjaWZpZWQgYG9uY2FuY2VsbGVkYCBjYWxsYmFjayB3aWxsIGJlIGRpc2NhcmRlZC5cbiAgICAgKiBTZWUge0BsaW5rIHRoZW59IGZvciBtb3JlIGRldGFpbHMuXG4gICAgICovXG4gICAgY2F0Y2g8VFJlc3VsdCA9IG5ldmVyPihvbnJlamVjdGVkPzogKChyZWFzb246IGFueSkgPT4gKFByb21pc2VMaWtlPFRSZXN1bHQ+IHwgVFJlc3VsdCkpIHwgdW5kZWZpbmVkIHwgbnVsbCwgb25jYW5jZWxsZWQ/OiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8VCB8IFRSZXN1bHQ+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMudGhlbih1bmRlZmluZWQsIG9ucmVqZWN0ZWQsIG9uY2FuY2VsbGVkKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRhY2hlcyBhIGNhbGxiYWNrIHRoYXQgaXMgaW52b2tlZCB3aGVuIHRoZSBDYW5jZWxsYWJsZVByb21pc2UgaXMgc2V0dGxlZCAoZnVsZmlsbGVkIG9yIHJlamVjdGVkKS4gVGhlXG4gICAgICogcmVzb2x2ZWQgdmFsdWUgY2Fubm90IGJlIGFjY2Vzc2VkIG9yIG1vZGlmaWVkIGZyb20gdGhlIGNhbGxiYWNrLlxuICAgICAqIFRoZSByZXR1cm5lZCBwcm9taXNlIHdpbGwgc2V0dGxlIGluIHRoZSBzYW1lIHN0YXRlIGFzIHRoZSBvcmlnaW5hbCBvbmVcbiAgICAgKiBhZnRlciB0aGUgcHJvdmlkZWQgY2FsbGJhY2sgaGFzIGNvbXBsZXRlZCBleGVjdXRpb24sXG4gICAgICogdW5sZXNzIHRoZSBjYWxsYmFjayB0aHJvd3Mgb3IgcmV0dXJucyBhIHJlamVjdGluZyBwcm9taXNlLFxuICAgICAqIGluIHdoaWNoIGNhc2UgdGhlIHJldHVybmVkIHByb21pc2Ugd2lsbCByZWplY3QgYXMgd2VsbC5cbiAgICAgKlxuICAgICAqIFRoZSBvcHRpb25hbCBgb25jYW5jZWxsZWRgIGFyZ3VtZW50IHdpbGwgYmUgaW52b2tlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCxcbiAgICAgKiB3aXRoIHRoZSBzYW1lIHNlbWFudGljcyBhcyB0aGUgYG9uY2FuY2VsbGVkYCBhcmd1bWVudCBvZiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogT25jZSB0aGUgcGFyZW50IHByb21pc2Ugc2V0dGxlcywgdGhlIGBvbmZpbmFsbHlgIGNhbGxiYWNrIHdpbGwgcnVuLFxuICAgICAqIF9ldmVuIGFmdGVyIHRoZSByZXR1cm5lZCBwcm9taXNlIGhhcyBiZWVuIGNhbmNlbGxlZDpfXG4gICAgICogaW4gdGhhdCBjYXNlLCBzaG91bGQgaXQgcmVqZWN0IG9yIHRocm93LCB0aGUgcmVhc29uIHdpbGwgYmUgd3JhcHBlZFxuICAgICAqIGluIGEge0BsaW5rIENhbmNlbGxlZFJlamVjdGlvbkVycm9yfSBhbmQgYnViYmxlZCB1cCBhcyBhbiB1bmhhbmRsZWQgcmVqZWN0aW9uLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgaXMgaW1wbGVtZW50ZWQgaW4gdGVybXMgb2Yge0BsaW5rIHRoZW59IGFuZCB0aGUgc2FtZSBjYXZlYXRzIGFwcGx5LlxuICAgICAqIEl0IGlzIHBvbHlmaWxsZWQsIGhlbmNlIGF2YWlsYWJsZSBpbiBldmVyeSBPUy93ZWJ2aWV3IHZlcnNpb24uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBBIFByb21pc2UgZm9yIHRoZSBjb21wbGV0aW9uIG9mIHRoZSBjYWxsYmFjay5cbiAgICAgKiBDYW5jZWxsYXRpb24gcmVxdWVzdHMgb24gdGhlIHJldHVybmVkIHByb21pc2VcbiAgICAgKiB3aWxsIHByb3BhZ2F0ZSB1cCB0aGUgY2hhaW4gdG8gdGhlIHBhcmVudCBwcm9taXNlLFxuICAgICAqIGJ1dCBub3QgaW4gdGhlIG90aGVyIGRpcmVjdGlvbi5cbiAgICAgKlxuICAgICAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20ge0BsaW5rIGNhbmNlbH0gd2lsbCBmdWxmaWxsIG9ubHkgYWZ0ZXIgYWxsIGF0dGFjaGVkIGhhbmRsZXJzXG4gICAgICogdXAgdGhlIGVudGlyZSBwcm9taXNlIGNoYWluIGhhdmUgYmVlbiBydW4uXG4gICAgICpcbiAgICAgKiBJZiBgb25maW5hbGx5YCByZXR1cm5zIGEgY2FuY2VsbGFibGUgcHJvbWlzZSxcbiAgICAgKiBjYW5jZWxsYXRpb24gcmVxdWVzdHMgd2lsbCBiZSBkaXZlcnRlZCB0byBpdCxcbiAgICAgKiBhbmQgdGhlIHNwZWNpZmllZCBgb25jYW5jZWxsZWRgIGNhbGxiYWNrIHdpbGwgYmUgZGlzY2FyZGVkLlxuICAgICAqIFNlZSB7QGxpbmsgdGhlbn0gZm9yIG1vcmUgZGV0YWlscy5cbiAgICAgKi9cbiAgICBmaW5hbGx5KG9uZmluYWxseT86ICgoKSA9PiB2b2lkKSB8IHVuZGVmaW5lZCB8IG51bGwsIG9uY2FuY2VsbGVkPzogQ2FuY2VsbGFibGVQcm9taXNlQ2FuY2VsbGVyKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgaWYgKCEodGhpcyBpbnN0YW5jZW9mIENhbmNlbGxhYmxlUHJvbWlzZSkpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5jZWxsYWJsZVByb21pc2UucHJvdG90eXBlLmZpbmFsbHkgY2FsbGVkIG9uIGFuIGludmFsaWQgb2JqZWN0LlwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghaXNDYWxsYWJsZShvbmZpbmFsbHkpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy50aGVuKG9uZmluYWxseSwgb25maW5hbGx5LCBvbmNhbmNlbGxlZCk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy50aGVuKFxuICAgICAgICAgICAgKHZhbHVlKSA9PiBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShvbmZpbmFsbHkoKSkudGhlbigoKSA9PiB2YWx1ZSksXG4gICAgICAgICAgICAocmVhc29uPykgPT4gQ2FuY2VsbGFibGVQcm9taXNlLnJlc29sdmUob25maW5hbGx5KCkpLnRoZW4oKCkgPT4geyB0aHJvdyByZWFzb247IH0pLFxuICAgICAgICAgICAgb25jYW5jZWxsZWQsXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogV2UgdXNlIHRoZSBgW1N5bWJvbC5zcGVjaWVzXWAgc3RhdGljIHByb3BlcnR5LCBpZiBhdmFpbGFibGUsXG4gICAgICogdG8gZGlzYWJsZSB0aGUgYnVpbHQtaW4gYXV0b21hdGljIHN1YmNsYXNzaW5nIGZlYXR1cmVzIGZyb20ge0BsaW5rIFByb21pc2V9LlxuICAgICAqIEl0IGlzIGNyaXRpY2FsIGZvciBwZXJmb3JtYW5jZSByZWFzb25zIHRoYXQgZXh0ZW5kZXJzIGRvIG5vdCBvdmVycmlkZSB0aGlzLlxuICAgICAqIE9uY2UgdGhlIHByb3Bvc2FsIGF0IGh0dHBzOi8vZ2l0aHViLmNvbS90YzM5L3Byb3Bvc2FsLXJtLWJ1aWx0aW4tc3ViY2xhc3NpbmdcbiAgICAgKiBpcyBlaXRoZXIgYWNjZXB0ZWQgb3IgcmV0aXJlZCwgdGhpcyBpbXBsZW1lbnRhdGlvbiB3aWxsIGhhdmUgdG8gYmUgcmV2aXNlZCBhY2NvcmRpbmdseS5cbiAgICAgKlxuICAgICAqIEBpZ25vcmVcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBzdGF0aWMgZ2V0IFtzcGVjaWVzXSgpIHtcbiAgICAgICAgcmV0dXJuIFByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGlzIHJlc29sdmVkIHdpdGggYW4gYXJyYXkgb2YgcmVzdWx0c1xuICAgICAqIHdoZW4gYWxsIG9mIHRoZSBwcm92aWRlZCBQcm9taXNlcyByZXNvbHZlLCBvciByZWplY3RlZCB3aGVuIGFueSBQcm9taXNlIGlzIHJlamVjdGVkLlxuICAgICAqXG4gICAgICogRXZlcnkgb25lIG9mIHRoZSBwcm92aWRlZCBvYmplY3RzIHRoYXQgaXMgYSB0aGVuYWJsZSBfYW5kXyBjYW5jZWxsYWJsZSBvYmplY3RcbiAgICAgKiB3aWxsIGJlIGNhbmNlbGxlZCB3aGVuIHRoZSByZXR1cm5lZCBwcm9taXNlIGlzIGNhbmNlbGxlZCwgd2l0aCB0aGUgc2FtZSBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBhbGw8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD5bXT47XG4gICAgc3RhdGljIGFsbDxUIGV4dGVuZHMgcmVhZG9ubHkgdW5rbm93bltdIHwgW10+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx7IC1yZWFkb25seSBbUCBpbiBrZXlvZiBUXTogQXdhaXRlZDxUW1BdPjsgfT47XG4gICAgc3RhdGljIGFsbDxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFsbChjb2xsZWN0ZWQpLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCBpcyByZXNvbHZlZCB3aXRoIGFuIGFycmF5IG9mIHJlc3VsdHNcbiAgICAgKiB3aGVuIGFsbCBvZiB0aGUgcHJvdmlkZWQgUHJvbWlzZXMgcmVzb2x2ZSBvciByZWplY3QuXG4gICAgICpcbiAgICAgKiBFdmVyeSBvbmUgb2YgdGhlIHByb3ZpZGVkIG9iamVjdHMgdGhhdCBpcyBhIHRoZW5hYmxlIF9hbmRfIGNhbmNlbGxhYmxlIG9iamVjdFxuICAgICAqIHdpbGwgYmUgY2FuY2VsbGVkIHdoZW4gdGhlIHJldHVybmVkIHByb21pc2UgaXMgY2FuY2VsbGVkLCB3aXRoIHRoZSBzYW1lIGNhdXNlLlxuICAgICAqXG4gICAgICogQGdyb3VwIFN0YXRpYyBNZXRob2RzXG4gICAgICovXG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VD4odmFsdWVzOiBJdGVyYWJsZTxUIHwgUHJvbWlzZUxpa2U8VD4+KTogQ2FuY2VsbGFibGVQcm9taXNlPFByb21pc2VTZXR0bGVkUmVzdWx0PEF3YWl0ZWQ8VD4+W10+O1xuICAgIHN0YXRpYyBhbGxTZXR0bGVkPFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPHsgLXJlYWRvbmx5IFtQIGluIGtleW9mIFRdOiBQcm9taXNlU2V0dGxlZFJlc3VsdDxBd2FpdGVkPFRbUF0+PjsgfT47XG4gICAgc3RhdGljIGFsbFNldHRsZWQ8VCBleHRlbmRzIEl0ZXJhYmxlPHVua25vd24+IHwgQXJyYXlMaWtlPHVua25vd24+Pih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4ge1xuICAgICAgICBsZXQgY29sbGVjdGVkID0gQXJyYXkuZnJvbSh2YWx1ZXMpO1xuICAgICAgICBjb25zdCBwcm9taXNlID0gY29sbGVjdGVkLmxlbmd0aCA9PT0gMFxuICAgICAgICAgICAgPyBDYW5jZWxsYWJsZVByb21pc2UucmVzb2x2ZShjb2xsZWN0ZWQpXG4gICAgICAgICAgICA6IG5ldyBDYW5jZWxsYWJsZVByb21pc2U8dW5rbm93bj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5hbGxTZXR0bGVkKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfSwgKGNhdXNlPyk6IFByb21pc2U8dm9pZD4gPT4gY2FuY2VsQWxsKHByb21pc2UsIGNvbGxlY3RlZCwgY2F1c2UpKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIGFueSBmdW5jdGlvbiByZXR1cm5zIGEgcHJvbWlzZSB0aGF0IGlzIGZ1bGZpbGxlZCBieSB0aGUgZmlyc3QgZ2l2ZW4gcHJvbWlzZSB0byBiZSBmdWxmaWxsZWQsXG4gICAgICogb3IgcmVqZWN0ZWQgd2l0aCBhbiBBZ2dyZWdhdGVFcnJvciBjb250YWluaW5nIGFuIGFycmF5IG9mIHJlamVjdGlvbiByZWFzb25zXG4gICAgICogaWYgYWxsIG9mIHRoZSBnaXZlbiBwcm9taXNlcyBhcmUgcmVqZWN0ZWQuXG4gICAgICogSXQgcmVzb2x2ZXMgYWxsIGVsZW1lbnRzIG9mIHRoZSBwYXNzZWQgaXRlcmFibGUgdG8gcHJvbWlzZXMgYXMgaXQgcnVucyB0aGlzIGFsZ29yaXRobS5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgYW55PFQ+KHZhbHVlczogSXRlcmFibGU8VCB8IFByb21pc2VMaWtlPFQ+Pik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PjtcbiAgICBzdGF0aWMgYW55PFQgZXh0ZW5kcyByZWFkb25seSB1bmtub3duW10gfCBbXT4odmFsdWVzOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VFtudW1iZXJdPj47XG4gICAgc3RhdGljIGFueTxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBjb2xsZWN0ZWQubGVuZ3RoID09PSAwXG4gICAgICAgICAgICA/IENhbmNlbGxhYmxlUHJvbWlzZS5yZXNvbHZlKGNvbGxlY3RlZClcbiAgICAgICAgICAgIDogbmV3IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdm9pZCBQcm9taXNlLmFueShjb2xsZWN0ZWQpLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgICAgIH0sIChjYXVzZT8pOiBQcm9taXNlPHZvaWQ+ID0+IGNhbmNlbEFsbChwcm9taXNlLCBjb2xsZWN0ZWQsIGNhdXNlKSk7XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBQcm9taXNlIHRoYXQgaXMgcmVzb2x2ZWQgb3IgcmVqZWN0ZWQgd2hlbiBhbnkgb2YgdGhlIHByb3ZpZGVkIFByb21pc2VzIGFyZSByZXNvbHZlZCBvciByZWplY3RlZC5cbiAgICAgKlxuICAgICAqIEV2ZXJ5IG9uZSBvZiB0aGUgcHJvdmlkZWQgb2JqZWN0cyB0aGF0IGlzIGEgdGhlbmFibGUgX2FuZF8gY2FuY2VsbGFibGUgb2JqZWN0XG4gICAgICogd2lsbCBiZSBjYW5jZWxsZWQgd2hlbiB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBjYW5jZWxsZWQsIHdpdGggdGhlIHNhbWUgY2F1c2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmFjZTxUPih2YWx1ZXM6IEl0ZXJhYmxlPFQgfCBQcm9taXNlTGlrZTxUPj4pOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUPj47XG4gICAgc3RhdGljIHJhY2U8VCBleHRlbmRzIHJlYWRvbmx5IHVua25vd25bXSB8IFtdPih2YWx1ZXM6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8QXdhaXRlZDxUW251bWJlcl0+PjtcbiAgICBzdGF0aWMgcmFjZTxUIGV4dGVuZHMgSXRlcmFibGU8dW5rbm93bj4gfCBBcnJheUxpa2U8dW5rbm93bj4+KHZhbHVlczogVCk6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiB7XG4gICAgICAgIGxldCBjb2xsZWN0ZWQgPSBBcnJheS5mcm9tKHZhbHVlcyk7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPHVua25vd24+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHZvaWQgUHJvbWlzZS5yYWNlKGNvbGxlY3RlZCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICB9LCAoY2F1c2U/KTogUHJvbWlzZTx2b2lkPiA9PiBjYW5jZWxBbGwocHJvbWlzZSwgY29sbGVjdGVkLCBjYXVzZSkpO1xuICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IGNhbmNlbGxlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCBjYXVzZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBjYW5jZWw8VCA9IG5ldmVyPihjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGNvbnN0IHAgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KCgpID0+IHt9KTtcbiAgICAgICAgcC5jYW5jZWwoY2F1c2UpO1xuICAgICAgICByZXR1cm4gcDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSB0aGF0IGNhbmNlbHNcbiAgICAgKiBhZnRlciB0aGUgc3BlY2lmaWVkIHRpbWVvdXQsIHdpdGggdGhlIHByb3ZpZGVkIGNhdXNlLlxuICAgICAqXG4gICAgICogSWYgdGhlIHtAbGluayBBYm9ydFNpZ25hbC50aW1lb3V0fSBmYWN0b3J5IG1ldGhvZCBpcyBhdmFpbGFibGUsXG4gICAgICogaXQgaXMgdXNlZCB0byBiYXNlIHRoZSB0aW1lb3V0IG9uIF9hY3RpdmVfIHRpbWUgcmF0aGVyIHRoYW4gX2VsYXBzZWRfIHRpbWUuXG4gICAgICogT3RoZXJ3aXNlLCBgdGltZW91dGAgZmFsbHMgYmFjayB0byB7QGxpbmsgc2V0VGltZW91dH0uXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgdGltZW91dDxUID0gbmV2ZXI+KG1pbGxpc2Vjb25kczogbnVtYmVyLCBjYXVzZT86IGFueSk6IENhbmNlbGxhYmxlUHJvbWlzZTxUPiB7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KCgpID0+IHt9KTtcbiAgICAgICAgaWYgKEFib3J0U2lnbmFsICYmIHR5cGVvZiBBYm9ydFNpZ25hbCA9PT0gJ2Z1bmN0aW9uJyAmJiBBYm9ydFNpZ25hbC50aW1lb3V0ICYmIHR5cGVvZiBBYm9ydFNpZ25hbC50aW1lb3V0ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgICBBYm9ydFNpZ25hbC50aW1lb3V0KG1pbGxpc2Vjb25kcykuYWRkRXZlbnRMaXN0ZW5lcignYWJvcnQnLCAoKSA9PiB2b2lkIHByb21pc2UuY2FuY2VsKGNhdXNlKSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHZvaWQgcHJvbWlzZS5jYW5jZWwoY2F1c2UpLCBtaWxsaXNlY29uZHMpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlIHRoYXQgcmVzb2x2ZXMgYWZ0ZXIgdGhlIHNwZWNpZmllZCB0aW1lb3V0LlxuICAgICAqIFRoZSByZXR1cm5lZCBwcm9taXNlIGNhbiBiZSBjYW5jZWxsZWQgd2l0aG91dCBjb25zZXF1ZW5jZXMuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgc2xlZXAobWlsbGlzZWNvbmRzOiBudW1iZXIpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBDYW5jZWxsYWJsZVByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlclxuICAgICAqIHRoZSBzcGVjaWZpZWQgdGltZW91dCwgd2l0aCB0aGUgcHJvdmlkZWQgdmFsdWUuXG4gICAgICogVGhlIHJldHVybmVkIHByb21pc2UgY2FuIGJlIGNhbmNlbGxlZCB3aXRob3V0IGNvbnNlcXVlbmNlcy5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyBzbGVlcDxUPihtaWxsaXNlY29uZHM6IG51bWJlciwgdmFsdWU6IFQpOiBDYW5jZWxsYWJsZVByb21pc2U8VD47XG4gICAgc3RhdGljIHNsZWVwPFQgPSB2b2lkPihtaWxsaXNlY29uZHM6IG51bWJlciwgdmFsdWU/OiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPFQ+IHtcbiAgICAgICAgcmV0dXJuIG5ldyBDYW5jZWxsYWJsZVByb21pc2U8VD4oKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gcmVzb2x2ZSh2YWx1ZSEpLCBtaWxsaXNlY29uZHMpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlamVjdGVkIENhbmNlbGxhYmxlUHJvbWlzZSBmb3IgdGhlIHByb3ZpZGVkIHJlYXNvbi5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZWplY3Q8VCA9IG5ldmVyPihyZWFzb24/OiBhbnkpOiBDYW5jZWxsYWJsZVByb21pc2U8VD4ge1xuICAgICAgICByZXR1cm4gbmV3IENhbmNlbGxhYmxlUHJvbWlzZTxUPigoXywgcmVqZWN0KSA9PiByZWplY3QocmVhc29uKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmVzb2x2ZSgpOiBDYW5jZWxsYWJsZVByb21pc2U8dm9pZD47XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyByZXNvbHZlZCBDYW5jZWxsYWJsZVByb21pc2UgZm9yIHRoZSBwcm92aWRlZCB2YWx1ZS5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyByZXNvbHZlPFQ+KHZhbHVlOiBUKTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcmVzb2x2ZWQgQ2FuY2VsbGFibGVQcm9taXNlIGZvciB0aGUgcHJvdmlkZWQgdmFsdWUuXG4gICAgICpcbiAgICAgKiBAZ3JvdXAgU3RhdGljIE1ldGhvZHNcbiAgICAgKi9cbiAgICBzdGF0aWMgcmVzb2x2ZTxUPih2YWx1ZTogVCB8IFByb21pc2VMaWtlPFQ+KTogQ2FuY2VsbGFibGVQcm9taXNlPEF3YWl0ZWQ8VD4+O1xuICAgIHN0YXRpYyByZXNvbHZlPFQgPSB2b2lkPih2YWx1ZT86IFQgfCBQcm9taXNlTGlrZTxUPik6IENhbmNlbGxhYmxlUHJvbWlzZTxBd2FpdGVkPFQ+PiB7XG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIENhbmNlbGxhYmxlUHJvbWlzZSkge1xuICAgICAgICAgICAgLy8gT3B0aW1pc2UgZm9yIGNhbmNlbGxhYmxlIHByb21pc2VzLlxuICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPGFueT4oKHJlc29sdmUpID0+IHJlc29sdmUodmFsdWUpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IENhbmNlbGxhYmxlUHJvbWlzZSBhbmQgcmV0dXJucyBpdCBpbiBhbiBvYmplY3QsIGFsb25nIHdpdGggaXRzIHJlc29sdmUgYW5kIHJlamVjdCBmdW5jdGlvbnNcbiAgICAgKiBhbmQgYSBnZXR0ZXIvc2V0dGVyIGZvciB0aGUgY2FuY2VsbGF0aW9uIGNhbGxiYWNrLlxuICAgICAqXG4gICAgICogVGhpcyBtZXRob2QgaXMgcG9seWZpbGxlZCwgaGVuY2UgYXZhaWxhYmxlIGluIGV2ZXJ5IE9TL3dlYnZpZXcgdmVyc2lvbi5cbiAgICAgKlxuICAgICAqIEBncm91cCBTdGF0aWMgTWV0aG9kc1xuICAgICAqL1xuICAgIHN0YXRpYyB3aXRoUmVzb2x2ZXJzPFQ+KCk6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4ge1xuICAgICAgICBsZXQgcmVzdWx0OiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+ID0geyBvbmNhbmNlbGxlZDogbnVsbCB9IGFzIGFueTtcbiAgICAgICAgcmVzdWx0LnByb21pc2UgPSBuZXcgQ2FuY2VsbGFibGVQcm9taXNlPFQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHJlc3VsdC5yZXNvbHZlID0gcmVzb2x2ZTtcbiAgICAgICAgICAgIHJlc3VsdC5yZWplY3QgPSByZWplY3Q7XG4gICAgICAgIH0sIChjYXVzZT86IGFueSkgPT4geyByZXN1bHQub25jYW5jZWxsZWQ/LihjYXVzZSk7IH0pO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSBjYW5jZWxsYXRpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gdGhlIHJlc3VsdGluZyBmdW5jdGlvbiBkb2VzIG5vdCByZWplY3QuXG4gKi9cbmZ1bmN0aW9uIGNhbmNlbGxlckZvcjxUPihwcm9taXNlOiBDYW5jZWxsYWJsZVByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+LCBzdGF0ZTogQ2FuY2VsbGFibGVQcm9taXNlU3RhdGUpIHtcbiAgICBsZXQgY2FuY2VsbGF0aW9uUHJvbWlzZTogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+ID0gdW5kZWZpbmVkO1xuXG4gICAgcmV0dXJuIChyZWFzb246IENhbmNlbEVycm9yKTogdm9pZCB8IFByb21pc2VMaWtlPHZvaWQ+ID0+IHtcbiAgICAgICAgaWYgKCFzdGF0ZS5zZXR0bGVkKSB7XG4gICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIHN0YXRlLnJlYXNvbiA9IHJlYXNvbjtcbiAgICAgICAgICAgIHByb21pc2UucmVqZWN0KHJlYXNvbik7XG5cbiAgICAgICAgICAgIC8vIEF0dGFjaCBhbiBlcnJvciBoYW5kbGVyIHRoYXQgaWdub3JlcyB0aGlzIHNwZWNpZmljIHJlamVjdGlvbiByZWFzb24gYW5kIG5vdGhpbmcgZWxzZS5cbiAgICAgICAgICAgIC8vIEluIHRoZW9yeSwgYSBzYW5lIHVuZGVybHlpbmcgaW1wbGVtZW50YXRpb24gYXQgdGhpcyBwb2ludFxuICAgICAgICAgICAgLy8gc2hvdWxkIGFsd2F5cyByZWplY3Qgd2l0aCBvdXIgY2FuY2VsbGF0aW9uIHJlYXNvbixcbiAgICAgICAgICAgIC8vIGhlbmNlIHRoZSBoYW5kbGVyIHdpbGwgbmV2ZXIgdGhyb3cuXG4gICAgICAgICAgICB2b2lkIFByb21pc2UucHJvdG90eXBlLnRoZW4uY2FsbChwcm9taXNlLnByb21pc2UsIHVuZGVmaW5lZCwgKGVycikgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChlcnIgIT09IHJlYXNvbikge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBlcnI7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBJZiByZWFzb24gaXMgbm90IHNldCwgdGhlIHByb21pc2UgcmVzb2x2ZWQgcmVndWxhcmx5LCBoZW5jZSB3ZSBtdXN0IG5vdCBjYWxsIG9uY2FuY2VsbGVkLlxuICAgICAgICAvLyBJZiBvbmNhbmNlbGxlZCBpcyB1bnNldCwgbm8gbmVlZCB0byBnbyBhbnkgZnVydGhlci5cbiAgICAgICAgaWYgKCFzdGF0ZS5yZWFzb24gfHwgIXByb21pc2Uub25jYW5jZWxsZWQpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgY2FuY2VsbGF0aW9uUHJvbWlzZSA9IG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJlc29sdmUocHJvbWlzZS5vbmNhbmNlbGxlZCEoc3RhdGUucmVhc29uIS5jYXVzZSkpO1xuICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHByb21pc2UucHJvbWlzZSwgZXJyLCBcIlVuaGFuZGxlZCBleGNlcHRpb24gaW4gb25jYW5jZWxsZWQgY2FsbGJhY2suXCIpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSkuY2F0Y2goKHJlYXNvbj8pID0+IHtcbiAgICAgICAgICAgIFByb21pc2UucmVqZWN0KG5ldyBDYW5jZWxsZWRSZWplY3Rpb25FcnJvcihwcm9taXNlLnByb21pc2UsIHJlYXNvbiwgXCJVbmhhbmRsZWQgcmVqZWN0aW9uIGluIG9uY2FuY2VsbGVkIGNhbGxiYWNrLlwiKSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFVuc2V0IG9uY2FuY2VsbGVkIHRvIHByZXZlbnQgcmVwZWF0ZWQgY2FsbHMuXG4gICAgICAgIHByb21pc2Uub25jYW5jZWxsZWQgPSBudWxsO1xuXG4gICAgICAgIHJldHVybiBjYW5jZWxsYXRpb25Qcm9taXNlO1xuICAgIH1cbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSByZXNvbHV0aW9uIGFsZ29yaXRobSBmb3IgdGhlIGdpdmVuIGNhbmNlbGxhYmxlIHByb21pc2UuXG4gKi9cbmZ1bmN0aW9uIHJlc29sdmVyRm9yPFQ+KHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZVdpdGhSZXNvbHZlcnM8VD4sIHN0YXRlOiBDYW5jZWxsYWJsZVByb21pc2VTdGF0ZSk6IENhbmNlbGxhYmxlUHJvbWlzZVJlc29sdmVyPFQ+IHtcbiAgICByZXR1cm4gKHZhbHVlKSA9PiB7XG4gICAgICAgIGlmIChzdGF0ZS5yZXNvbHZpbmcpIHsgcmV0dXJuOyB9XG4gICAgICAgIHN0YXRlLnJlc29sdmluZyA9IHRydWU7XG5cbiAgICAgICAgaWYgKHZhbHVlID09PSBwcm9taXNlLnByb21pc2UpIHtcbiAgICAgICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7IHJldHVybjsgfVxuICAgICAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgICAgICBwcm9taXNlLnJlamVjdChuZXcgVHlwZUVycm9yKFwiQSBwcm9taXNlIGNhbm5vdCBiZSByZXNvbHZlZCB3aXRoIGl0c2VsZi5cIikpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHZhbHVlICE9IG51bGwgJiYgKHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgfHwgdHlwZW9mIHZhbHVlID09PSAnZnVuY3Rpb24nKSkge1xuICAgICAgICAgICAgbGV0IHRoZW46IGFueTtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgdGhlbiA9ICh2YWx1ZSBhcyBhbnkpLnRoZW47XG4gICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICBzdGF0ZS5zZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBwcm9taXNlLnJlamVjdChlcnIpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGlzQ2FsbGFibGUodGhlbikpIHtcbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICBsZXQgY2FuY2VsID0gKHZhbHVlIGFzIGFueSkuY2FuY2VsO1xuICAgICAgICAgICAgICAgICAgICBpZiAoaXNDYWxsYWJsZShjYW5jZWwpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBvbmNhbmNlbGxlZCA9IChjYXVzZT86IGFueSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZmxlY3QuYXBwbHkoY2FuY2VsLCB2YWx1ZSwgW2NhdXNlXSk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHN0YXRlLnJlYXNvbikge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIElmIGFscmVhZHkgY2FuY2VsbGVkLCBwcm9wYWdhdGUgY2FuY2VsbGF0aW9uLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIFRoZSBwcm9taXNlIHJldHVybmVkIGZyb20gdGhlIGNhbmNlbGxlciBhbGdvcml0aG0gZG9lcyBub3QgcmVqZWN0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gc28gaXQgY2FuIGJlIGRpc2NhcmRlZCBzYWZlbHkuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdm9pZCBjYW5jZWxsZXJGb3IoeyAuLi5wcm9taXNlLCBvbmNhbmNlbGxlZCB9LCBzdGF0ZSkoc3RhdGUucmVhc29uKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvbWlzZS5vbmNhbmNlbGxlZCA9IG9uY2FuY2VsbGVkO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSBjYXRjaCB7fVxuXG4gICAgICAgICAgICAgICAgY29uc3QgbmV3U3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlID0ge1xuICAgICAgICAgICAgICAgICAgICByb290OiBzdGF0ZS5yb290LFxuICAgICAgICAgICAgICAgICAgICByZXNvbHZpbmc6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBnZXQgc2V0dGxlZCgpIHsgcmV0dXJuIHRoaXMucm9vdC5zZXR0bGVkIH0sXG4gICAgICAgICAgICAgICAgICAgIHNldCBzZXR0bGVkKHZhbHVlKSB7IHRoaXMucm9vdC5zZXR0bGVkID0gdmFsdWU7IH0sXG4gICAgICAgICAgICAgICAgICAgIGdldCByZWFzb24oKSB7IHJldHVybiB0aGlzLnJvb3QucmVhc29uIH1cbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgY29uc3QgcmVqZWN0b3IgPSByZWplY3RvckZvcihwcm9taXNlLCBuZXdTdGF0ZSk7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgUmVmbGVjdC5hcHBseSh0aGVuLCB2YWx1ZSwgW3Jlc29sdmVyRm9yKHByb21pc2UsIG5ld1N0YXRlKSwgcmVqZWN0b3JdKTtcbiAgICAgICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVqZWN0b3IoZXJyKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuOyAvLyBJTVBPUlRBTlQhXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc3RhdGUuc2V0dGxlZCkgeyByZXR1cm47IH1cbiAgICAgICAgc3RhdGUuc2V0dGxlZCA9IHRydWU7XG4gICAgICAgIHByb21pc2UucmVzb2x2ZSh2YWx1ZSk7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgY2FsbGJhY2sgdGhhdCBpbXBsZW1lbnRzIHRoZSByZWplY3Rpb24gYWxnb3JpdGhtIGZvciB0aGUgZ2l2ZW4gY2FuY2VsbGFibGUgcHJvbWlzZS5cbiAqL1xuZnVuY3Rpb24gcmVqZWN0b3JGb3I8VD4ocHJvbWlzZTogQ2FuY2VsbGFibGVQcm9taXNlV2l0aFJlc29sdmVyczxUPiwgc3RhdGU6IENhbmNlbGxhYmxlUHJvbWlzZVN0YXRlKTogQ2FuY2VsbGFibGVQcm9taXNlUmVqZWN0b3Ige1xuICAgIHJldHVybiAocmVhc29uPykgPT4ge1xuICAgICAgICBpZiAoc3RhdGUucmVzb2x2aW5nKSB7IHJldHVybjsgfVxuICAgICAgICBzdGF0ZS5yZXNvbHZpbmcgPSB0cnVlO1xuXG4gICAgICAgIGlmIChzdGF0ZS5zZXR0bGVkKSB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIGlmIChyZWFzb24gaW5zdGFuY2VvZiBDYW5jZWxFcnJvciAmJiBzdGF0ZS5yZWFzb24gaW5zdGFuY2VvZiBDYW5jZWxFcnJvciAmJiBPYmplY3QuaXMocmVhc29uLmNhdXNlLCBzdGF0ZS5yZWFzb24uY2F1c2UpKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIFN3YWxsb3cgbGF0ZSByZWplY3Rpb25zIHRoYXQgYXJlIENhbmNlbEVycm9ycyB3aG9zZSBjYW5jZWxsYXRpb24gY2F1c2UgaXMgdGhlIHNhbWUgYXMgb3Vycy5cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gY2F0Y2gge31cblxuICAgICAgICAgICAgdm9pZCBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocHJvbWlzZS5wcm9taXNlLCByZWFzb24pKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHN0YXRlLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAgICAgcHJvbWlzZS5yZWplY3QocmVhc29uKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBDYW5jZWxzIGFsbCB2YWx1ZXMgaW4gYW4gYXJyYXkgdGhhdCBsb29rIGxpa2UgY2FuY2VsbGFibGUgdGhlbmFibGVzLlxuICogUmV0dXJucyBhIHByb21pc2UgdGhhdCBmdWxmaWxscyBvbmNlIGFsbCBjYW5jZWxsYXRpb24gcHJvY2VkdXJlcyBmb3IgdGhlIGdpdmVuIHZhbHVlcyBoYXZlIHNldHRsZWQuXG4gKi9cbmZ1bmN0aW9uIGNhbmNlbEFsbChwYXJlbnQ6IENhbmNlbGxhYmxlUHJvbWlzZTx1bmtub3duPiwgdmFsdWVzOiBhbnlbXSwgY2F1c2U/OiBhbnkpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZXN1bHRzID0gW107XG5cbiAgICBmb3IgKGNvbnN0IHZhbHVlIG9mIHZhbHVlcykge1xuICAgICAgICBsZXQgY2FuY2VsOiBDYW5jZWxsYWJsZVByb21pc2VDYW5jZWxsZXI7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBpZiAoIWlzQ2FsbGFibGUodmFsdWUudGhlbikpIHsgY29udGludWU7IH1cbiAgICAgICAgICAgIGNhbmNlbCA9IHZhbHVlLmNhbmNlbDtcbiAgICAgICAgICAgIGlmICghaXNDYWxsYWJsZShjYW5jZWwpKSB7IGNvbnRpbnVlOyB9XG4gICAgICAgIH0gY2F0Y2ggeyBjb250aW51ZTsgfVxuXG4gICAgICAgIGxldCByZXN1bHQ6IHZvaWQgfCBQcm9taXNlTGlrZTx2b2lkPjtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJlc3VsdCA9IFJlZmxlY3QuYXBwbHkoY2FuY2VsLCB2YWx1ZSwgW2NhdXNlXSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgUHJvbWlzZS5yZWplY3QobmV3IENhbmNlbGxlZFJlamVjdGlvbkVycm9yKHBhcmVudCwgZXJyLCBcIlVuaGFuZGxlZCBleGNlcHRpb24gaW4gY2FuY2VsIG1ldGhvZC5cIikpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXJlc3VsdCkgeyBjb250aW51ZTsgfVxuICAgICAgICByZXN1bHRzLnB1c2goXG4gICAgICAgICAgICAocmVzdWx0IGluc3RhbmNlb2YgUHJvbWlzZSAgPyByZXN1bHQgOiBQcm9taXNlLnJlc29sdmUocmVzdWx0KSkuY2F0Y2goKHJlYXNvbj8pID0+IHtcbiAgICAgICAgICAgICAgICBQcm9taXNlLnJlamVjdChuZXcgQ2FuY2VsbGVkUmVqZWN0aW9uRXJyb3IocGFyZW50LCByZWFzb24sIFwiVW5oYW5kbGVkIHJlamVjdGlvbiBpbiBjYW5jZWwgbWV0aG9kLlwiKSk7XG4gICAgICAgICAgICB9KVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHJldHVybiBQcm9taXNlLmFsbChyZXN1bHRzKSBhcyBhbnk7XG59XG5cbi8qKlxuICogUmV0dXJucyBpdHMgYXJndW1lbnQuXG4gKi9cbmZ1bmN0aW9uIGlkZW50aXR5PFQ+KHg6IFQpOiBUIHtcbiAgICByZXR1cm4geDtcbn1cblxuLyoqXG4gKiBUaHJvd3MgaXRzIGFyZ3VtZW50LlxuICovXG5mdW5jdGlvbiB0aHJvd2VyKHJlYXNvbj86IGFueSk6IG5ldmVyIHtcbiAgICB0aHJvdyByZWFzb247XG59XG5cbi8qKlxuICogQXR0ZW1wdHMgdmFyaW91cyBzdHJhdGVnaWVzIHRvIGNvbnZlcnQgYW4gZXJyb3IgdG8gYSBzdHJpbmcuXG4gKi9cbmZ1bmN0aW9uIGVycm9yTWVzc2FnZShlcnI6IGFueSk6IHN0cmluZyB7XG4gICAgdHJ5IHtcbiAgICAgICAgaWYgKGVyciBpbnN0YW5jZW9mIEVycm9yIHx8IHR5cGVvZiBlcnIgIT09ICdvYmplY3QnIHx8IGVyci50b1N0cmluZyAhPT0gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZykge1xuICAgICAgICAgICAgcmV0dXJuIFwiXCIgKyBlcnI7XG4gICAgICAgIH1cbiAgICB9IGNhdGNoIHt9XG5cbiAgICB0cnkge1xuICAgICAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkoZXJyKTtcbiAgICB9IGNhdGNoIHt9XG5cbiAgICB0cnkge1xuICAgICAgICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKGVycik7XG4gICAgfSBjYXRjaCB7fVxuXG4gICAgcmV0dXJuIFwiPGNvdWxkIG5vdCBjb252ZXJ0IGVycm9yIHRvIHN0cmluZz5cIjtcbn1cblxuLyoqXG4gKiBHZXRzIHRoZSBjdXJyZW50IGJhcnJpZXIgcHJvbWlzZSBmb3IgdGhlIGdpdmVuIGNhbmNlbGxhYmxlIHByb21pc2UuIElmIG5lY2Vzc2FyeSwgaW5pdGlhbGlzZXMgdGhlIGJhcnJpZXIuXG4gKi9cbmZ1bmN0aW9uIGN1cnJlbnRCYXJyaWVyPFQ+KHByb21pc2U6IENhbmNlbGxhYmxlUHJvbWlzZTxUPik6IFByb21pc2U8dm9pZD4ge1xuICAgIGxldCBwd3I6IFBhcnRpYWw8UHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4+ID0gcHJvbWlzZVtiYXJyaWVyU3ltXSA/PyB7fTtcbiAgICBpZiAoISgncHJvbWlzZScgaW4gcHdyKSkge1xuICAgICAgICBPYmplY3QuYXNzaWduKHB3ciwgcHJvbWlzZVdpdGhSZXNvbHZlcnM8dm9pZD4oKSk7XG4gICAgfVxuICAgIGlmIChwcm9taXNlW2JhcnJpZXJTeW1dID09IG51bGwpIHtcbiAgICAgICAgcHdyLnJlc29sdmUhKCk7XG4gICAgICAgIHByb21pc2VbYmFycmllclN5bV0gPSBwd3I7XG4gICAgfVxuICAgIHJldHVybiBwd3IucHJvbWlzZSE7XG59XG5cbi8vIFBvbHlmaWxsIFByb21pc2Uud2l0aFJlc29sdmVycy5cbmxldCBwcm9taXNlV2l0aFJlc29sdmVycyA9IFByb21pc2Uud2l0aFJlc29sdmVycztcbmlmIChwcm9taXNlV2l0aFJlc29sdmVycyAmJiB0eXBlb2YgcHJvbWlzZVdpdGhSZXNvbHZlcnMgPT09ICdmdW5jdGlvbicpIHtcbiAgICBwcm9taXNlV2l0aFJlc29sdmVycyA9IHByb21pc2VXaXRoUmVzb2x2ZXJzLmJpbmQoUHJvbWlzZSk7XG59IGVsc2Uge1xuICAgIHByb21pc2VXaXRoUmVzb2x2ZXJzID0gZnVuY3Rpb24gPFQ+KCk6IFByb21pc2VXaXRoUmVzb2x2ZXJzPFQ+IHtcbiAgICAgICAgbGV0IHJlc29sdmUhOiAodmFsdWU6IFQgfCBQcm9taXNlTGlrZTxUPikgPT4gdm9pZDtcbiAgICAgICAgbGV0IHJlamVjdCE6IChyZWFzb24/OiBhbnkpID0+IHZvaWQ7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgUHJvbWlzZTxUPigocmVzLCByZWopID0+IHsgcmVzb2x2ZSA9IHJlczsgcmVqZWN0ID0gcmVqOyB9KTtcbiAgICAgICAgcmV0dXJuIHsgcHJvbWlzZSwgcmVzb2x2ZSwgcmVqZWN0IH07XG4gICAgfVxufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyLCBvYmplY3ROYW1lc30gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuXG5jb25zdCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihvYmplY3ROYW1lcy5DbGlwYm9hcmQpO1xuXG5jb25zdCBDbGlwYm9hcmRTZXRUZXh0ID0gMDtcbmNvbnN0IENsaXBib2FyZFRleHQgPSAxO1xuXG4vKipcbiAqIFNldHMgdGhlIHRleHQgdG8gdGhlIENsaXBib2FyZC5cbiAqXG4gKiBAcGFyYW0gdGV4dCAtIFRoZSB0ZXh0IHRvIGJlIHNldCB0byB0aGUgQ2xpcGJvYXJkLlxuICogQHJldHVybiBBIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgc3VjY2Vzc2Z1bC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNldFRleHQodGV4dDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkU2V0VGV4dCwge3RleHR9KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgdGV4dCBmcm9tIHRoZSBDbGlwYm9hcmQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBUZXh0KCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIGNhbGwoQ2xpcGJvYXJkVGV4dCk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qKlxuICogQW55IGlzIGEgZHVtbXkgY3JlYXRpb24gZnVuY3Rpb24gZm9yIHNpbXBsZSBvciB1bmtub3duIHR5cGVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gQW55PFQgPSBhbnk+KHNvdXJjZTogYW55KTogVCB7XG4gICAgcmV0dXJuIHNvdXJjZTtcbn1cblxuLyoqXG4gKiBCeXRlU2xpY2UgaXMgYSBjcmVhdGlvbiBmdW5jdGlvbiB0aGF0IHJlcGxhY2VzXG4gKiBudWxsIHN0cmluZ3Mgd2l0aCBlbXB0eSBzdHJpbmdzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gQnl0ZVNsaWNlKHNvdXJjZTogYW55KTogc3RyaW5nIHtcbiAgICByZXR1cm4gKChzb3VyY2UgPT0gbnVsbCkgPyBcIlwiIDogc291cmNlKTtcbn1cblxuLyoqXG4gKiBBcnJheSB0YWtlcyBhIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhbiBhcmJpdHJhcnkgdHlwZVxuICogYW5kIHJldHVybnMgYW4gaW4tcGxhY2UgY3JlYXRpb24gZnVuY3Rpb24gZm9yIGFuIGFycmF5XG4gKiB3aG9zZSBlbGVtZW50cyBhcmUgb2YgdGhhdCB0eXBlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gQXJyYXk8VCA9IGFueT4oZWxlbWVudDogKHNvdXJjZTogYW55KSA9PiBUKTogKHNvdXJjZTogYW55KSA9PiBUW10ge1xuICAgIGlmIChlbGVtZW50ID09PSBBbnkpIHtcbiAgICAgICAgcmV0dXJuIChzb3VyY2UpID0+IChzb3VyY2UgPT09IG51bGwgPyBbXSA6IHNvdXJjZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChzb3VyY2UpID0+IHtcbiAgICAgICAgaWYgKHNvdXJjZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgICB9XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc291cmNlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBzb3VyY2VbaV0gPSBlbGVtZW50KHNvdXJjZVtpXSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNvdXJjZTtcbiAgICB9O1xufVxuXG4vKipcbiAqIE1hcCB0YWtlcyBjcmVhdGlvbiBmdW5jdGlvbnMgZm9yIHR3byBhcmJpdHJhcnkgdHlwZXNcbiAqIGFuZCByZXR1cm5zIGFuIGluLXBsYWNlIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhbiBvYmplY3RcbiAqIHdob3NlIGtleXMgYW5kIHZhbHVlcyBhcmUgb2YgdGhvc2UgdHlwZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBNYXA8ViA9IGFueT4oa2V5OiAoc291cmNlOiBhbnkpID0+IHN0cmluZywgdmFsdWU6IChzb3VyY2U6IGFueSkgPT4gVik6IChzb3VyY2U6IGFueSkgPT4gUmVjb3JkPHN0cmluZywgVj4ge1xuICAgIGlmICh2YWx1ZSA9PT0gQW55KSB7XG4gICAgICAgIHJldHVybiAoc291cmNlKSA9PiAoc291cmNlID09PSBudWxsID8ge30gOiBzb3VyY2UpO1xuICAgIH1cblxuICAgIHJldHVybiAoc291cmNlKSA9PiB7XG4gICAgICAgIGlmIChzb3VyY2UgPT09IG51bGwpIHtcbiAgICAgICAgICAgIHJldHVybiB7fTtcbiAgICAgICAgfVxuICAgICAgICBmb3IgKGNvbnN0IGtleSBpbiBzb3VyY2UpIHtcbiAgICAgICAgICAgIHNvdXJjZVtrZXldID0gdmFsdWUoc291cmNlW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzb3VyY2U7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBOdWxsYWJsZSB0YWtlcyBhIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhbiBhcmJpdHJhcnkgdHlwZVxuICogYW5kIHJldHVybnMgYSBjcmVhdGlvbiBmdW5jdGlvbiBmb3IgYSBudWxsYWJsZSB2YWx1ZSBvZiB0aGF0IHR5cGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBOdWxsYWJsZTxUID0gYW55PihlbGVtZW50OiAoc291cmNlOiBhbnkpID0+IFQpOiAoc291cmNlOiBhbnkpID0+IChUIHwgbnVsbCkge1xuICAgIGlmIChlbGVtZW50ID09PSBBbnkpIHtcbiAgICAgICAgcmV0dXJuIEFueTtcbiAgICB9XG5cbiAgICByZXR1cm4gKHNvdXJjZSkgPT4gKHNvdXJjZSA9PT0gbnVsbCA/IG51bGwgOiBlbGVtZW50KHNvdXJjZSkpO1xufVxuXG4vKipcbiAqIFN0cnVjdCB0YWtlcyBhbiBvYmplY3QgbWFwcGluZyBmaWVsZCBuYW1lcyB0byBjcmVhdGlvbiBmdW5jdGlvbnNcbiAqIGFuZCByZXR1cm5zIGFuIGluLXBsYWNlIGNyZWF0aW9uIGZ1bmN0aW9uIGZvciBhIHN0cnVjdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFN0cnVjdChjcmVhdGVGaWVsZDogUmVjb3JkPHN0cmluZywgKHNvdXJjZTogYW55KSA9PiBhbnk+KTpcbiAgICA8VSBleHRlbmRzIFJlY29yZDxzdHJpbmcsIGFueT4gPSBhbnk+KHNvdXJjZTogYW55KSA9PiBVXG57XG4gICAgbGV0IGFsbEFueSA9IHRydWU7XG4gICAgZm9yIChjb25zdCBuYW1lIGluIGNyZWF0ZUZpZWxkKSB7XG4gICAgICAgIGlmIChjcmVhdGVGaWVsZFtuYW1lXSAhPT0gQW55KSB7XG4gICAgICAgICAgICBhbGxBbnkgPSBmYWxzZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgfVxuICAgIGlmIChhbGxBbnkpIHtcbiAgICAgICAgcmV0dXJuIEFueTtcbiAgICB9XG5cbiAgICByZXR1cm4gKHNvdXJjZSkgPT4ge1xuICAgICAgICBmb3IgKGNvbnN0IG5hbWUgaW4gY3JlYXRlRmllbGQpIHtcbiAgICAgICAgICAgIGlmIChuYW1lIGluIHNvdXJjZSkge1xuICAgICAgICAgICAgICAgIHNvdXJjZVtuYW1lXSA9IGNyZWF0ZUZpZWxkW25hbWVdKHNvdXJjZVtuYW1lXSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNvdXJjZTtcbiAgICB9O1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFNpemUge1xuICAgIC8qKiBUaGUgd2lkdGggb2YgYSByZWN0YW5ndWxhciBhcmVhLiAqL1xuICAgIFdpZHRoOiBudW1iZXI7XG4gICAgLyoqIFRoZSBoZWlnaHQgb2YgYSByZWN0YW5ndWxhciBhcmVhLiAqL1xuICAgIEhlaWdodDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFJlY3Qge1xuICAgIC8qKiBUaGUgWCBjb29yZGluYXRlIG9mIHRoZSBvcmlnaW4uICovXG4gICAgWDogbnVtYmVyO1xuICAgIC8qKiBUaGUgWSBjb29yZGluYXRlIG9mIHRoZSBvcmlnaW4uICovXG4gICAgWTogbnVtYmVyO1xuICAgIC8qKiBUaGUgd2lkdGggb2YgdGhlIHJlY3RhbmdsZS4gKi9cbiAgICBXaWR0aDogbnVtYmVyO1xuICAgIC8qKiBUaGUgaGVpZ2h0IG9mIHRoZSByZWN0YW5nbGUuICovXG4gICAgSGVpZ2h0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2NyZWVuIHtcbiAgICAvKiogVW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBzY3JlZW4uICovXG4gICAgSUQ6IHN0cmluZztcbiAgICAvKiogSHVtYW4tcmVhZGFibGUgbmFtZSBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIE5hbWU6IHN0cmluZztcbiAgICAvKiogVGhlIHNjYWxlIGZhY3RvciBvZiB0aGUgc2NyZWVuIChEUEkvOTYpLiAxID0gc3RhbmRhcmQgRFBJLCAyID0gSGlEUEkgKFJldGluYSksIGV0Yy4gKi9cbiAgICBTY2FsZUZhY3RvcjogbnVtYmVyO1xuICAgIC8qKiBUaGUgWCBjb29yZGluYXRlIG9mIHRoZSBzY3JlZW4uICovXG4gICAgWDogbnVtYmVyO1xuICAgIC8qKiBUaGUgWSBjb29yZGluYXRlIG9mIHRoZSBzY3JlZW4uICovXG4gICAgWTogbnVtYmVyO1xuICAgIC8qKiBDb250YWlucyB0aGUgd2lkdGggYW5kIGhlaWdodCBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIFNpemU6IFNpemU7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBib3VuZHMgb2YgdGhlIHNjcmVlbiBpbiB0ZXJtcyBvZiBYLCBZLCBXaWR0aCwgYW5kIEhlaWdodC4gKi9cbiAgICBCb3VuZHM6IFJlY3Q7XG4gICAgLyoqIENvbnRhaW5zIHRoZSBwaHlzaWNhbCBib3VuZHMgb2YgdGhlIHNjcmVlbiBpbiB0ZXJtcyBvZiBYLCBZLCBXaWR0aCwgYW5kIEhlaWdodCAoYmVmb3JlIHNjYWxpbmcpLiAqL1xuICAgIFBoeXNpY2FsQm91bmRzOiBSZWN0O1xuICAgIC8qKiBDb250YWlucyB0aGUgYXJlYSBvZiB0aGUgc2NyZWVuIHRoYXQgaXMgYWN0dWFsbHkgdXNhYmxlIChleGNsdWRpbmcgdGFza2JhciBhbmQgb3RoZXIgc3lzdGVtIFVJKS4gKi9cbiAgICBXb3JrQXJlYTogUmVjdDtcbiAgICAvKiogQ29udGFpbnMgdGhlIHBoeXNpY2FsIFdvcmtBcmVhIG9mIHRoZSBzY3JlZW4gKGJlZm9yZSBzY2FsaW5nKS4gKi9cbiAgICBQaHlzaWNhbFdvcmtBcmVhOiBSZWN0O1xuICAgIC8qKiBUcnVlIGlmIHRoaXMgaXMgdGhlIHByaW1hcnkgbW9uaXRvciBzZWxlY3RlZCBieSB0aGUgdXNlciBpbiB0aGUgb3BlcmF0aW5nIHN5c3RlbS4gKi9cbiAgICBJc1ByaW1hcnk6IGJvb2xlYW47XG4gICAgLyoqIFRoZSByb3RhdGlvbiBvZiB0aGUgc2NyZWVuLiAqL1xuICAgIFJvdGF0aW9uOiBudW1iZXI7XG59XG5cbmltcG9ydCB7IG5ld1J1bnRpbWVDYWxsZXIsIG9iamVjdE5hbWVzIH0gZnJvbSBcIi4vcnVudGltZS5qc1wiO1xuY29uc3QgY2FsbCA9IG5ld1J1bnRpbWVDYWxsZXIob2JqZWN0TmFtZXMuU2NyZWVucyk7XG5cbmNvbnN0IGdldEFsbCA9IDA7XG5jb25zdCBnZXRQcmltYXJ5ID0gMTtcbmNvbnN0IGdldEN1cnJlbnQgPSAyO1xuXG4vKipcbiAqIEdldHMgYWxsIHNjcmVlbnMuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gYW4gYXJyYXkgb2YgU2NyZWVuIG9iamVjdHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRBbGwoKTogUHJvbWlzZTxTY3JlZW5bXT4ge1xuICAgIHJldHVybiBjYWxsKGdldEFsbCk7XG59XG5cbi8qKlxuICogR2V0cyB0aGUgcHJpbWFyeSBzY3JlZW4uXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gdGhlIHByaW1hcnkgc2NyZWVuLlxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0UHJpbWFyeSgpOiBQcm9taXNlPFNjcmVlbj4ge1xuICAgIHJldHVybiBjYWxsKGdldFByaW1hcnkpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBjdXJyZW50IGFjdGl2ZSBzY3JlZW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBHZXRDdXJyZW50KCk6IFByb21pc2U8U2NyZWVuPiB7XG4gICAgcmV0dXJuIGNhbGwoZ2V0Q3VycmVudCk7XG59XG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNBQTtBQUFBO0FBQUE7QUFBQTs7O0FDNkJBLElBQU0sY0FDRjtBQUVHLFNBQVMsT0FBTyxPQUFlLElBQVk7QUFDOUMsTUFBSSxLQUFLO0FBRVQsTUFBSSxJQUFJLE9BQU87QUFDZixTQUFPLEtBQUs7QUFFUixVQUFNLFlBQWEsS0FBSyxPQUFPLElBQUksS0FBTSxDQUFDO0FBQUEsRUFDOUM7QUFDQSxTQUFPO0FBQ1g7OztBQzdCQSxJQUFNLGFBQWEsT0FBTyxTQUFTLFNBQVM7QUFHckMsSUFBTSxjQUFjLE9BQU8sT0FBTztBQUFBLEVBQ3JDLE1BQU07QUFBQSxFQUNOLFdBQVc7QUFBQSxFQUNYLGFBQWE7QUFBQSxFQUNiLFFBQVE7QUFBQSxFQUNSLGFBQWE7QUFBQSxFQUNiLFFBQVE7QUFBQSxFQUNSLFFBQVE7QUFBQSxFQUNSLFNBQVM7QUFBQSxFQUNULFFBQVE7QUFBQSxFQUNSLFNBQVM7QUFBQSxFQUNULFlBQVk7QUFDaEIsQ0FBQztBQUNNLElBQUksV0FBVyxPQUFPO0FBU3RCLFNBQVMsaUJBQWlCLFFBQWdCLGFBQXFCLElBQUk7QUFDdEUsU0FBTyxTQUFVLFFBQWdCLE9BQVksTUFBTTtBQUMvQyxXQUFPLGtCQUFrQixRQUFRLFFBQVEsWUFBWSxJQUFJO0FBQUEsRUFDN0Q7QUFDSjtBQUVBLGVBQWUsa0JBQWtCLFVBQWtCLFFBQWdCLFlBQW9CLE1BQXlCO0FBM0NoSCxNQUFBQSxLQUFBO0FBNENJLE1BQUksTUFBTSxJQUFJLElBQUksVUFBVTtBQUM1QixNQUFJLGFBQWEsT0FBTyxVQUFVLFNBQVMsU0FBUyxDQUFDO0FBQ3JELE1BQUksYUFBYSxPQUFPLFVBQVUsT0FBTyxTQUFTLENBQUM7QUFDbkQsTUFBSSxNQUFNO0FBQUUsUUFBSSxhQUFhLE9BQU8sUUFBUSxLQUFLLFVBQVUsSUFBSSxDQUFDO0FBQUEsRUFBRztBQUVuRSxNQUFJLFVBQWtDO0FBQUEsSUFDbEMsQ0FBQyxtQkFBbUIsR0FBRztBQUFBLEVBQzNCO0FBQ0EsTUFBSSxZQUFZO0FBQ1osWUFBUSxxQkFBcUIsSUFBSTtBQUFBLEVBQ3JDO0FBRUEsTUFBSSxXQUFXLE1BQU0sTUFBTSxLQUFLLEVBQUUsUUFBUSxDQUFDO0FBQzNDLE1BQUksQ0FBQyxTQUFTLElBQUk7QUFDZCxVQUFNLElBQUksTUFBTSxNQUFNLFNBQVMsS0FBSyxDQUFDO0FBQUEsRUFDekM7QUFFQSxRQUFLLE1BQUFBLE1BQUEsU0FBUyxRQUFRLElBQUksY0FBYyxNQUFuQyxnQkFBQUEsSUFBc0MsUUFBUSx3QkFBOUMsWUFBcUUsUUFBUSxJQUFJO0FBQ2xGLFdBQU8sU0FBUyxLQUFLO0FBQUEsRUFDekIsT0FBTztBQUNILFdBQU8sU0FBUyxLQUFLO0FBQUEsRUFDekI7QUFDSjs7O0FGdERBLElBQU0sT0FBTyxpQkFBaUIsWUFBWSxPQUFPO0FBRWpELElBQU0saUJBQWlCO0FBT2hCLFNBQVMsUUFBUSxLQUFrQztBQUN0RCxTQUFPLEtBQUssZ0JBQWdCLEVBQUMsS0FBSyxJQUFJLFNBQVMsRUFBQyxDQUFDO0FBQ3JEOzs7QUd2QkE7QUFBQTtBQUFBLGVBQUFDO0FBQUEsRUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFjQSxPQUFPLFNBQVMsT0FBTyxVQUFVLENBQUM7QUFDbEMsT0FBTyxPQUFPLHNCQUFzQjtBQUNwQyxPQUFPLE9BQU8sdUJBQXVCO0FBSXJDLElBQU1DLFFBQU8saUJBQWlCLFlBQVksTUFBTTtBQUNoRCxJQUFNLGtCQUFrQixvQkFBSSxJQUE4QjtBQUcxRCxJQUFNLGFBQWE7QUFDbkIsSUFBTSxnQkFBZ0I7QUFDdEIsSUFBTSxjQUFjO0FBQ3BCLElBQU0saUJBQWlCO0FBQ3ZCLElBQU0saUJBQWlCO0FBQ3ZCLElBQU0saUJBQWlCO0FBMEd2QixTQUFTLHFCQUFxQixJQUFZLE1BQWMsUUFBdUI7QUFDM0UsTUFBSSxZQUFZLHFCQUFxQixFQUFFO0FBQ3ZDLE1BQUksQ0FBQyxXQUFXO0FBQ1o7QUFBQSxFQUNKO0FBRUEsTUFBSSxRQUFRO0FBQ1IsUUFBSTtBQUNBLGdCQUFVLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLElBQ3RDLFNBQVMsS0FBVTtBQUNmLGdCQUFVLE9BQU8sSUFBSSxVQUFVLDZCQUE2QixJQUFJLFNBQVMsRUFBRSxPQUFPLElBQUksQ0FBQyxDQUFDO0FBQUEsSUFDNUY7QUFBQSxFQUNKLE9BQU87QUFDSCxjQUFVLFFBQVEsSUFBSTtBQUFBLEVBQzFCO0FBQ0o7QUFRQSxTQUFTLG9CQUFvQixJQUFZLFNBQXVCO0FBOUpoRSxNQUFBQztBQStKSSxHQUFBQSxNQUFBLHFCQUFxQixFQUFFLE1BQXZCLGdCQUFBQSxJQUEwQixPQUFPLElBQUksT0FBTyxNQUFNLE9BQU87QUFDN0Q7QUFRQSxTQUFTLHFCQUFxQixJQUEwQztBQUNwRSxRQUFNLFdBQVcsZ0JBQWdCLElBQUksRUFBRTtBQUN2QyxrQkFBZ0IsT0FBTyxFQUFFO0FBQ3pCLFNBQU87QUFDWDtBQU9BLFNBQVMsYUFBcUI7QUFDMUIsTUFBSTtBQUNKLEtBQUc7QUFDQyxhQUFTLE9BQU87QUFBQSxFQUNwQixTQUFTLGdCQUFnQixJQUFJLE1BQU07QUFDbkMsU0FBTztBQUNYO0FBU0EsU0FBUyxPQUFPLE1BQWMsVUFBZ0YsQ0FBQyxHQUFpQjtBQUM1SCxRQUFNLEtBQUssV0FBVztBQUN0QixTQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxvQkFBZ0IsSUFBSSxJQUFJLEVBQUUsU0FBUyxPQUFPLENBQUM7QUFDM0MsSUFBQUQsTUFBSyxNQUFNLE9BQU8sT0FBTyxFQUFFLGFBQWEsR0FBRyxHQUFHLE9BQU8sQ0FBQyxFQUFFLE1BQU0sQ0FBQyxRQUFhO0FBQ3hFLHNCQUFnQixPQUFPLEVBQUU7QUFDekIsYUFBTyxHQUFHO0FBQUEsSUFDZCxDQUFDO0FBQUEsRUFDTCxDQUFDO0FBQ0w7QUFRTyxTQUFTLEtBQUssU0FBZ0Q7QUFBRSxTQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUc7QUFRcEcsU0FBUyxRQUFRLFNBQWdEO0FBQUUsU0FBTyxPQUFPLGVBQWUsT0FBTztBQUFHO0FBUTFHLFNBQVNFLE9BQU0sU0FBZ0Q7QUFBRSxTQUFPLE9BQU8sYUFBYSxPQUFPO0FBQUc7QUFRdEcsU0FBUyxTQUFTLFNBQWdEO0FBQUUsU0FBTyxPQUFPLGdCQUFnQixPQUFPO0FBQUc7QUFXNUcsU0FBUyxTQUFTLFNBQTREO0FBdFByRixNQUFBRDtBQXNQdUYsVUFBT0EsTUFBQSxPQUFPLGdCQUFnQixPQUFPLE1BQTlCLE9BQUFBLE1BQW1DLENBQUM7QUFBRztBQVE5SCxTQUFTLFNBQVMsU0FBaUQ7QUFBRSxTQUFPLE9BQU8sZ0JBQWdCLE9BQU87QUFBRzs7O0FDOVBwSDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7QUNhTyxJQUFNLGlCQUFpQixvQkFBSSxJQUF3QjtBQUVuRCxJQUFNLFdBQU4sTUFBZTtBQUFBLEVBS2xCLFlBQVksV0FBbUIsVUFBK0IsY0FBc0I7QUFDaEYsU0FBSyxZQUFZO0FBQ2pCLFNBQUssV0FBVztBQUNoQixTQUFLLGVBQWUsZ0JBQWdCO0FBQUEsRUFDeEM7QUFBQSxFQUVBLFNBQVMsTUFBb0I7QUFDekIsUUFBSTtBQUNBLFdBQUssU0FBUyxJQUFJO0FBQUEsSUFDdEIsU0FBUyxLQUFLO0FBQ1YsY0FBUSxNQUFNLEdBQUc7QUFBQSxJQUNyQjtBQUVBLFFBQUksS0FBSyxpQkFBaUIsR0FBSSxRQUFPO0FBQ3JDLFNBQUssZ0JBQWdCO0FBQ3JCLFdBQU8sS0FBSyxpQkFBaUI7QUFBQSxFQUNqQztBQUNKO0FBRU8sU0FBUyxZQUFZLFVBQTBCO0FBQ2xELE1BQUksWUFBWSxlQUFlLElBQUksU0FBUyxTQUFTO0FBQ3JELE1BQUksQ0FBQyxXQUFXO0FBQ1o7QUFBQSxFQUNKO0FBRUEsY0FBWSxVQUFVLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDaEQsTUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixtQkFBZSxPQUFPLFNBQVMsU0FBUztBQUFBLEVBQzVDLE9BQU87QUFDSCxtQkFBZSxJQUFJLFNBQVMsV0FBVyxTQUFTO0FBQUEsRUFDcEQ7QUFDSjs7O0FDdENPLElBQU0sUUFBUSxPQUFPLE9BQU87QUFBQSxFQUNsQyxTQUFTLE9BQU8sT0FBTztBQUFBLElBQ3RCLHVCQUF1QjtBQUFBLElBQ3ZCLHNCQUFzQjtBQUFBLElBQ3RCLG9CQUFvQjtBQUFBLElBQ3BCLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLG9CQUFvQjtBQUFBLElBQ3BCLG9CQUFvQjtBQUFBLElBQ3BCLDRCQUE0QjtBQUFBLElBQzVCLGNBQWM7QUFBQSxJQUNkLHVCQUF1QjtBQUFBLElBQ3ZCLG1CQUFtQjtBQUFBLElBQ25CLGVBQWU7QUFBQSxJQUNmLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLElBQ2pCLGtCQUFrQjtBQUFBLElBQ2xCLGdCQUFnQjtBQUFBLElBQ2hCLGlCQUFpQjtBQUFBLElBQ2pCLGlCQUFpQjtBQUFBLElBQ2pCLGdCQUFnQjtBQUFBLElBQ2hCLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLElBQ2pCLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLGdCQUFnQjtBQUFBLElBQ2hCLGVBQWU7QUFBQSxJQUNmLGFBQWE7QUFBQSxJQUNiLGlCQUFpQjtBQUFBLElBQ2pCLG9CQUFvQjtBQUFBLElBQ3BCLDBCQUEwQjtBQUFBLElBQzFCLDJCQUEyQjtBQUFBLElBQzNCLDBCQUEwQjtBQUFBLElBQzFCLHdCQUF3QjtBQUFBLElBQ3hCLGFBQWE7QUFBQSxJQUNiLGVBQWU7QUFBQSxJQUNmLGdCQUFnQjtBQUFBLElBQ2hCLFlBQVk7QUFBQSxJQUNaLGlCQUFpQjtBQUFBLElBQ2pCLG1CQUFtQjtBQUFBLElBQ25CLG9CQUFvQjtBQUFBLElBQ3BCLHFCQUFxQjtBQUFBLElBQ3JCLGdCQUFnQjtBQUFBLElBQ2hCLGtCQUFrQjtBQUFBLElBQ2xCLGdCQUFnQjtBQUFBLElBQ2hCLGtCQUFrQjtBQUFBLEVBQ25CLENBQUM7QUFBQSxFQUNELEtBQUssT0FBTyxPQUFPO0FBQUEsSUFDbEIsNEJBQTRCO0FBQUEsSUFDNUIsdUNBQXVDO0FBQUEsSUFDdkMseUNBQXlDO0FBQUEsSUFDekMsMEJBQTBCO0FBQUEsSUFDMUIsb0NBQW9DO0FBQUEsSUFDcEMsc0NBQXNDO0FBQUEsSUFDdEMsb0NBQW9DO0FBQUEsSUFDcEMsMENBQTBDO0FBQUEsSUFDMUMsMkJBQTJCO0FBQUEsSUFDM0IsK0JBQStCO0FBQUEsSUFDL0Isb0JBQW9CO0FBQUEsSUFDcEIsNEJBQTRCO0FBQUEsSUFDNUIsc0JBQXNCO0FBQUEsSUFDdEIsc0JBQXNCO0FBQUEsSUFDdEIsK0JBQStCO0FBQUEsSUFDL0IsNkJBQTZCO0FBQUEsSUFDN0IsZ0NBQWdDO0FBQUEsSUFDaEMscUJBQXFCO0FBQUEsSUFDckIsNkJBQTZCO0FBQUEsSUFDN0IsMEJBQTBCO0FBQUEsSUFDMUIsdUJBQXVCO0FBQUEsSUFDdkIsdUJBQXVCO0FBQUEsSUFDdkIsZ0JBQWdCO0FBQUEsSUFDaEIsc0JBQXNCO0FBQUEsSUFDdEIsY0FBYztBQUFBLElBQ2Qsb0JBQW9CO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsc0JBQXNCO0FBQUEsSUFDdEIsYUFBYTtBQUFBLElBQ2IsY0FBYztBQUFBLElBQ2QsbUJBQW1CO0FBQUEsSUFDbkIsbUJBQW1CO0FBQUEsSUFDbkIseUJBQXlCO0FBQUEsSUFDekIsZUFBZTtBQUFBLElBQ2YsaUJBQWlCO0FBQUEsSUFDakIsdUJBQXVCO0FBQUEsSUFDdkIscUJBQXFCO0FBQUEsSUFDckIscUJBQXFCO0FBQUEsSUFDckIsdUJBQXVCO0FBQUEsSUFDdkIsY0FBYztBQUFBLElBQ2QsZUFBZTtBQUFBLElBQ2Ysb0JBQW9CO0FBQUEsSUFDcEIsb0JBQW9CO0FBQUEsSUFDcEIsMEJBQTBCO0FBQUEsSUFDMUIsZ0JBQWdCO0FBQUEsSUFDaEIsNEJBQTRCO0FBQUEsSUFDNUIsNEJBQTRCO0FBQUEsSUFDNUIseURBQXlEO0FBQUEsSUFDekQsc0NBQXNDO0FBQUEsSUFDdEMsb0JBQW9CO0FBQUEsSUFDcEIscUJBQXFCO0FBQUEsSUFDckIscUJBQXFCO0FBQUEsSUFDckIsc0JBQXNCO0FBQUEsSUFDdEIsZ0NBQWdDO0FBQUEsSUFDaEMsa0NBQWtDO0FBQUEsSUFDbEMsbUNBQW1DO0FBQUEsSUFDbkMsb0NBQW9DO0FBQUEsSUFDcEMsK0JBQStCO0FBQUEsSUFDL0IsNkJBQTZCO0FBQUEsSUFDN0IsdUJBQXVCO0FBQUEsSUFDdkIsaUNBQWlDO0FBQUEsSUFDakMsOEJBQThCO0FBQUEsSUFDOUIsNEJBQTRCO0FBQUEsSUFDNUIsc0NBQXNDO0FBQUEsSUFDdEMsNEJBQTRCO0FBQUEsSUFDNUIsc0JBQXNCO0FBQUEsSUFDdEIsa0NBQWtDO0FBQUEsSUFDbEMsc0JBQXNCO0FBQUEsSUFDdEIsd0JBQXdCO0FBQUEsSUFDeEIsd0JBQXdCO0FBQUEsSUFDeEIsbUJBQW1CO0FBQUEsSUFDbkIsMEJBQTBCO0FBQUEsSUFDMUIsOEJBQThCO0FBQUEsSUFDOUIseUJBQXlCO0FBQUEsSUFDekIsNkJBQTZCO0FBQUEsSUFDN0IsaUJBQWlCO0FBQUEsSUFDakIsZ0JBQWdCO0FBQUEsSUFDaEIsc0JBQXNCO0FBQUEsSUFDdEIsZUFBZTtBQUFBLElBQ2YseUJBQXlCO0FBQUEsSUFDekIsd0JBQXdCO0FBQUEsSUFDeEIsb0JBQW9CO0FBQUEsSUFDcEIscUJBQXFCO0FBQUEsSUFDckIsaUJBQWlCO0FBQUEsSUFDakIsaUJBQWlCO0FBQUEsSUFDakIsc0JBQXNCO0FBQUEsSUFDdEIsbUNBQW1DO0FBQUEsSUFDbkMscUNBQXFDO0FBQUEsSUFDckMsdUJBQXVCO0FBQUEsSUFDdkIsc0JBQXNCO0FBQUEsSUFDdEIsd0JBQXdCO0FBQUEsSUFDeEIsZUFBZTtBQUFBLElBQ2YsMkJBQTJCO0FBQUEsSUFDM0IsMEJBQTBCO0FBQUEsSUFDMUIsNkJBQTZCO0FBQUEsSUFDN0IsWUFBWTtBQUFBLElBQ1osZ0JBQWdCO0FBQUEsSUFDaEIsa0JBQWtCO0FBQUEsSUFDbEIsZ0JBQWdCO0FBQUEsSUFDaEIsa0JBQWtCO0FBQUEsSUFDbEIsbUJBQW1CO0FBQUEsSUFDbkIsWUFBWTtBQUFBLElBQ1oscUJBQXFCO0FBQUEsSUFDckIsc0JBQXNCO0FBQUEsSUFDdEIsc0JBQXNCO0FBQUEsSUFDdEIsOEJBQThCO0FBQUEsSUFDOUIsaUJBQWlCO0FBQUEsSUFDakIseUJBQXlCO0FBQUEsSUFDekIsMkJBQTJCO0FBQUEsSUFDM0IsK0JBQStCO0FBQUEsSUFDL0IsMEJBQTBCO0FBQUEsSUFDMUIsOEJBQThCO0FBQUEsSUFDOUIsaUJBQWlCO0FBQUEsSUFDakIsdUJBQXVCO0FBQUEsSUFDdkIsZ0JBQWdCO0FBQUEsSUFDaEIsMEJBQTBCO0FBQUEsSUFDMUIseUJBQXlCO0FBQUEsSUFDekIsc0JBQXNCO0FBQUEsSUFDdEIsa0JBQWtCO0FBQUEsSUFDbEIsbUJBQW1CO0FBQUEsSUFDbkIsa0JBQWtCO0FBQUEsSUFDbEIsdUJBQXVCO0FBQUEsSUFDdkIsb0NBQW9DO0FBQUEsSUFDcEMsc0NBQXNDO0FBQUEsSUFDdEMsd0JBQXdCO0FBQUEsSUFDeEIsdUJBQXVCO0FBQUEsSUFDdkIseUJBQXlCO0FBQUEsSUFDekIsNEJBQTRCO0FBQUEsSUFDNUIsNEJBQTRCO0FBQUEsSUFDNUIsY0FBYztBQUFBLElBQ2QsZUFBZTtBQUFBLElBQ2YsaUJBQWlCO0FBQUEsRUFDbEIsQ0FBQztBQUFBLEVBQ0QsT0FBTyxPQUFPLE9BQU87QUFBQSxJQUNwQixvQkFBb0I7QUFBQSxJQUNwQixvQkFBb0I7QUFBQSxJQUNwQixtQkFBbUI7QUFBQSxJQUNuQixlQUFlO0FBQUEsSUFDZixpQkFBaUI7QUFBQSxJQUNqQixlQUFlO0FBQUEsSUFDZixnQkFBZ0I7QUFBQSxJQUNoQixtQkFBbUI7QUFBQSxFQUNwQixDQUFDO0FBQUEsRUFDRCxRQUFRLE9BQU8sT0FBTztBQUFBLElBQ3JCLDJCQUEyQjtBQUFBLElBQzNCLG9CQUFvQjtBQUFBLElBQ3BCLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLElBQ2pCLGtCQUFrQjtBQUFBLElBQ2xCLG9CQUFvQjtBQUFBLElBQ3BCLGFBQWE7QUFBQSxJQUNiLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLGlCQUFpQjtBQUFBLElBQ2pCLGdCQUFnQjtBQUFBLElBQ2hCLGdCQUFnQjtBQUFBLElBQ2hCLHVCQUF1QjtBQUFBLElBQ3ZCLGVBQWU7QUFBQSxJQUNmLG9CQUFvQjtBQUFBLElBQ3BCLFlBQVk7QUFBQSxJQUNaLG9CQUFvQjtBQUFBLElBQ3BCLGtCQUFrQjtBQUFBLElBQ2xCLGtCQUFrQjtBQUFBLElBQ2xCLFlBQVk7QUFBQSxJQUNaLGNBQWM7QUFBQSxJQUNkLGVBQWU7QUFBQSxJQUNmLGlCQUFpQjtBQUFBLEVBQ2xCLENBQUM7QUFDRixDQUFDOzs7QUZ6TkQsT0FBTyxTQUFTLE9BQU8sVUFBVSxDQUFDO0FBQ2xDLE9BQU8sT0FBTyxxQkFBcUI7QUFFbkMsSUFBTUUsUUFBTyxpQkFBaUIsWUFBWSxNQUFNO0FBQ2hELElBQU0sYUFBYTtBQVlaLElBQU0sYUFBTixNQUFpQjtBQUFBLEVBaUJwQixZQUFZLE1BQWMsT0FBWSxNQUFNO0FBQ3hDLFNBQUssT0FBTztBQUNaLFNBQUssT0FBTztBQUFBLEVBQ2hCO0FBQ0o7QUFFQSxTQUFTLG1CQUFtQixPQUFZO0FBQ3BDLE1BQUksWUFBWSxlQUFlLElBQUksTUFBTSxJQUFJO0FBQzdDLE1BQUksQ0FBQyxXQUFXO0FBQ1o7QUFBQSxFQUNKO0FBRUEsTUFBSSxhQUFhLElBQUksV0FBVyxNQUFNLE1BQU0sTUFBTSxJQUFJO0FBQ3RELE1BQUksWUFBWSxPQUFPO0FBQ25CLGVBQVcsU0FBUyxNQUFNO0FBQUEsRUFDOUI7QUFFQSxjQUFZLFVBQVUsT0FBTyxjQUFZLENBQUMsU0FBUyxTQUFTLFVBQVUsQ0FBQztBQUN2RSxNQUFJLFVBQVUsV0FBVyxHQUFHO0FBQ3hCLG1CQUFlLE9BQU8sTUFBTSxJQUFJO0FBQUEsRUFDcEMsT0FBTztBQUNILG1CQUFlLElBQUksTUFBTSxNQUFNLFNBQVM7QUFBQSxFQUM1QztBQUNKO0FBVU8sU0FBUyxXQUFXLFdBQW1CLFVBQW9CLGNBQXNCO0FBQ3BGLE1BQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsUUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxZQUFVLEtBQUssWUFBWTtBQUMzQixpQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxTQUFPLE1BQU0sWUFBWSxZQUFZO0FBQ3pDO0FBU08sU0FBUyxHQUFHLFdBQW1CLFVBQWdDO0FBQ2xFLFNBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUM3QztBQVNPLFNBQVMsS0FBSyxXQUFtQixVQUFnQztBQUNwRSxTQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFDNUM7QUFPTyxTQUFTLE9BQU8sWUFBeUM7QUFDNUQsYUFBVyxRQUFRLGVBQWEsZUFBZSxPQUFPLFNBQVMsQ0FBQztBQUNwRTtBQUtPLFNBQVMsU0FBZTtBQUMzQixpQkFBZSxNQUFNO0FBQ3pCO0FBU08sU0FBUyxLQUFLLE1BQWMsTUFBMkI7QUFDMUQsTUFBSTtBQUVKLE1BQUksT0FBTyxTQUFTLFlBQVksU0FBUyxRQUFRLFVBQVUsUUFBUSxVQUFVLE1BQU07QUFFL0UsWUFBUSxJQUFJLFdBQVcsS0FBSyxNQUFNLEdBQUcsS0FBSyxNQUFNLENBQUM7QUFBQSxFQUNyRCxPQUFPO0FBRUgsWUFBUSxJQUFJLFdBQVcsTUFBZ0IsSUFBSTtBQUFBLEVBQy9DO0FBRUEsU0FBT0EsTUFBSyxZQUFZLEtBQUs7QUFDakM7OztBR2xJTyxTQUFTLFNBQVMsU0FBYztBQUVuQyxVQUFRO0FBQUEsSUFDSixrQkFBa0IsVUFBVTtBQUFBLElBQzVCO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFDSjtBQU1PLFNBQVMsa0JBQTJCO0FBQ3ZDLFNBQVEsSUFBSSxXQUFXLFdBQVcsRUFBRyxZQUFZO0FBQ3JEO0FBTU8sU0FBUyxvQkFBb0I7QUFDaEMsTUFBSSxDQUFDLGVBQWUsQ0FBQyxlQUFlLENBQUM7QUFDakMsV0FBTztBQUVYLE1BQUksU0FBUztBQUViLFFBQU0sU0FBUyxJQUFJLFlBQVk7QUFDL0IsUUFBTSxhQUFhLElBQUksZ0JBQWdCO0FBQ3ZDLFNBQU8saUJBQWlCLFFBQVEsTUFBTTtBQUFFLGFBQVM7QUFBQSxFQUFPLEdBQUcsRUFBRSxRQUFRLFdBQVcsT0FBTyxDQUFDO0FBQ3hGLGFBQVcsTUFBTTtBQUNqQixTQUFPLGNBQWMsSUFBSSxZQUFZLE1BQU0sQ0FBQztBQUU1QyxTQUFPO0FBQ1g7QUFLTyxTQUFTLFlBQVksT0FBMkI7QUF0RHZELE1BQUFDO0FBdURJLE1BQUksTUFBTSxrQkFBa0IsYUFBYTtBQUNyQyxXQUFPLE1BQU07QUFBQSxFQUNqQixXQUFXLEVBQUUsTUFBTSxrQkFBa0IsZ0JBQWdCLE1BQU0sa0JBQWtCLE1BQU07QUFDL0UsWUFBT0EsTUFBQSxNQUFNLE9BQU8sa0JBQWIsT0FBQUEsTUFBOEIsU0FBUztBQUFBLEVBQ2xELE9BQU87QUFDSCxXQUFPLFNBQVM7QUFBQSxFQUNwQjtBQUNKO0FBaUNBLElBQUksVUFBVTtBQUNkLFNBQVMsaUJBQWlCLG9CQUFvQixNQUFNO0FBQUUsWUFBVTtBQUFLLENBQUM7QUFFL0QsU0FBUyxVQUFVLFVBQXNCO0FBQzVDLE1BQUksV0FBVyxTQUFTLGVBQWUsWUFBWTtBQUMvQyxhQUFTO0FBQUEsRUFDYixPQUFPO0FBQ0gsYUFBUyxpQkFBaUIsb0JBQW9CLFFBQVE7QUFBQSxFQUMxRDtBQUNKOzs7QUMzRkEsSUFBTSxpQkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGNBQW9DO0FBQzFDLElBQU0sK0JBQW9DO0FBQzFDLElBQU0sOEJBQW9DO0FBQzFDLElBQU0sY0FBb0M7QUFDMUMsSUFBTSxvQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxrQkFBb0M7QUFDMUMsSUFBTSxnQkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sa0JBQW9DO0FBQzFDLElBQU0scUJBQW9DO0FBQzFDLElBQU0sb0JBQW9DO0FBQzFDLElBQU0sb0JBQW9DO0FBQzFDLElBQU0saUJBQW9DO0FBQzFDLElBQU0saUJBQW9DO0FBQzFDLElBQU0sYUFBb0M7QUFDMUMsSUFBTSxxQkFBb0M7QUFDMUMsSUFBTSx5QkFBb0M7QUFDMUMsSUFBTSxlQUFvQztBQUMxQyxJQUFNLGtCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLG9CQUFvQztBQUMxQyxJQUFNLHVCQUFvQztBQUMxQyxJQUFNLDRCQUFvQztBQUMxQyxJQUFNLHFCQUFvQztBQUMxQyxJQUFNLG1DQUFvQztBQUMxQyxJQUFNLG1CQUFvQztBQUMxQyxJQUFNLG1CQUFvQztBQUMxQyxJQUFNLDRCQUFvQztBQUMxQyxJQUFNLHFCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLGlCQUFvQztBQUMxQyxJQUFNLGdCQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sYUFBb0M7QUFDMUMsSUFBTSx5QkFBb0M7QUFDMUMsSUFBTSx1QkFBb0M7QUFDMUMsSUFBTSx3QkFBb0M7QUFDMUMsSUFBTSxxQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxtQkFBb0M7QUFDMUMsSUFBTSxjQUFvQztBQUMxQyxJQUFNLGFBQW9DO0FBQzFDLElBQU0sZUFBb0M7QUFDMUMsSUFBTSxnQkFBb0M7QUFDMUMsSUFBTSxrQkFBb0M7QUF1QjFDLElBQU0sWUFBWSxPQUFPLFFBQVE7QUFJcEI7QUFGYixJQUFNLFVBQU4sTUFBTSxRQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFVVCxZQUFZLE9BQWUsSUFBSTtBQUMzQixTQUFLLFNBQVMsSUFBSSxpQkFBaUIsWUFBWSxRQUFRLElBQUk7QUFHM0QsZUFBVyxVQUFVLE9BQU8sb0JBQW9CLFFBQU8sU0FBUyxHQUFHO0FBQy9ELFVBQ0ksV0FBVyxpQkFDUixPQUFRLEtBQWEsTUFBTSxNQUFNLFlBQ3RDO0FBQ0UsUUFBQyxLQUFhLE1BQU0sSUFBSyxLQUFhLE1BQU0sRUFBRSxLQUFLLElBQUk7QUFBQSxNQUMzRDtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxJQUFJLE1BQXNCO0FBQ3RCLFdBQU8sSUFBSSxRQUFPLElBQUk7QUFBQSxFQUMxQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLFdBQThCO0FBQzFCLFdBQU8sS0FBSyxTQUFTLEVBQUUsY0FBYztBQUFBLEVBQ3pDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxTQUF3QjtBQUNwQixXQUFPLEtBQUssU0FBUyxFQUFFLFlBQVk7QUFBQSxFQUN2QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsUUFBdUI7QUFDbkIsV0FBTyxLQUFLLFNBQVMsRUFBRSxXQUFXO0FBQUEsRUFDdEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLHlCQUF3QztBQUNwQyxXQUFPLEtBQUssU0FBUyxFQUFFLDRCQUE0QjtBQUFBLEVBQ3ZEO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSx3QkFBdUM7QUFDbkMsV0FBTyxLQUFLLFNBQVMsRUFBRSwyQkFBMkI7QUFBQSxFQUN0RDtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsUUFBdUI7QUFDbkIsV0FBTyxLQUFLLFNBQVMsRUFBRSxXQUFXO0FBQUEsRUFDdEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGNBQTZCO0FBQ3pCLFdBQU8sS0FBSyxTQUFTLEVBQUUsaUJBQWlCO0FBQUEsRUFDNUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGFBQTRCO0FBQ3hCLFdBQU8sS0FBSyxTQUFTLEVBQUUsZ0JBQWdCO0FBQUEsRUFDM0M7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxZQUE2QjtBQUN6QixXQUFPLEtBQUssU0FBUyxFQUFFLGVBQWU7QUFBQSxFQUMxQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLFVBQTJCO0FBQ3ZCLFdBQU8sS0FBSyxTQUFTLEVBQUUsYUFBYTtBQUFBLEVBQ3hDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsU0FBMEI7QUFDdEIsV0FBTyxLQUFLLFNBQVMsRUFBRSxZQUFZO0FBQUEsRUFDdkM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLE9BQXNCO0FBQ2xCLFdBQU8sS0FBSyxTQUFTLEVBQUUsVUFBVTtBQUFBLEVBQ3JDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsWUFBOEI7QUFDMUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxlQUFlO0FBQUEsRUFDMUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxlQUFpQztBQUM3QixXQUFPLEtBQUssU0FBUyxFQUFFLGtCQUFrQjtBQUFBLEVBQzdDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsY0FBZ0M7QUFDNUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxpQkFBaUI7QUFBQSxFQUM1QztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLGNBQWdDO0FBQzVCLFdBQU8sS0FBSyxTQUFTLEVBQUUsaUJBQWlCO0FBQUEsRUFDNUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFdBQTBCO0FBQ3RCLFdBQU8sS0FBSyxTQUFTLEVBQUUsY0FBYztBQUFBLEVBQ3pDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxXQUEwQjtBQUN0QixXQUFPLEtBQUssU0FBUyxFQUFFLGNBQWM7QUFBQSxFQUN6QztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLE9BQXdCO0FBQ3BCLFdBQU8sS0FBSyxTQUFTLEVBQUUsVUFBVTtBQUFBLEVBQ3JDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxlQUE4QjtBQUMxQixXQUFPLEtBQUssU0FBUyxFQUFFLGtCQUFrQjtBQUFBLEVBQzdDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsbUJBQXNDO0FBQ2xDLFdBQU8sS0FBSyxTQUFTLEVBQUUsc0JBQXNCO0FBQUEsRUFDakQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFNBQXdCO0FBQ3BCLFdBQU8sS0FBSyxTQUFTLEVBQUUsWUFBWTtBQUFBLEVBQ3ZDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsWUFBOEI7QUFDMUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxlQUFlO0FBQUEsRUFDMUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFVBQXlCO0FBQ3JCLFdBQU8sS0FBSyxTQUFTLEVBQUUsYUFBYTtBQUFBLEVBQ3hDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRQSxZQUFZLEdBQVcsR0FBMEI7QUFDN0MsV0FBTyxLQUFLLFNBQVMsRUFBRSxtQkFBbUIsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQUFBLEVBQ3REO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsZUFBZSxhQUFxQztBQUNoRCxXQUFPLEtBQUssU0FBUyxFQUFFLHNCQUFzQixFQUFFLFlBQVksQ0FBQztBQUFBLEVBQ2hFO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBVUEsb0JBQW9CLEdBQVcsR0FBVyxHQUFXLEdBQTBCO0FBQzNFLFdBQU8sS0FBSyxTQUFTLEVBQUUsMkJBQTJCLEVBQUUsR0FBRyxHQUFHLEdBQUcsRUFBRSxDQUFDO0FBQUEsRUFDcEU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxhQUFhLFdBQW1DO0FBQzVDLFdBQU8sS0FBSyxTQUFTLEVBQUUsb0JBQW9CLEVBQUUsVUFBVSxDQUFDO0FBQUEsRUFDNUQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSwyQkFBMkIsU0FBaUM7QUFDeEQsV0FBTyxLQUFLLFNBQVMsRUFBRSxrQ0FBa0MsRUFBRSxRQUFRLENBQUM7QUFBQSxFQUN4RTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsV0FBVyxPQUFlLFFBQStCO0FBQ3JELFdBQU8sS0FBSyxTQUFTLEVBQUUsa0JBQWtCLEVBQUUsT0FBTyxPQUFPLENBQUM7QUFBQSxFQUM5RDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsV0FBVyxPQUFlLFFBQStCO0FBQ3JELFdBQU8sS0FBSyxTQUFTLEVBQUUsa0JBQWtCLEVBQUUsT0FBTyxPQUFPLENBQUM7QUFBQSxFQUM5RDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsb0JBQW9CLEdBQVcsR0FBMEI7QUFDckQsV0FBTyxLQUFLLFNBQVMsRUFBRSwyQkFBMkIsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQUFBLEVBQzlEO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsYUFBYUMsWUFBbUM7QUFDNUMsV0FBTyxLQUFLLFNBQVMsRUFBRSxvQkFBb0IsRUFBRSxXQUFBQSxXQUFVLENBQUM7QUFBQSxFQUM1RDtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsUUFBUSxPQUFlLFFBQStCO0FBQ2xELFdBQU8sS0FBSyxTQUFTLEVBQUUsZUFBZSxFQUFFLE9BQU8sT0FBTyxDQUFDO0FBQUEsRUFDM0Q7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxTQUFTLE9BQThCO0FBQ25DLFdBQU8sS0FBSyxTQUFTLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0FBQUEsRUFDcEQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxRQUFRLE1BQTZCO0FBQ2pDLFdBQU8sS0FBSyxTQUFTLEVBQUUsZUFBZSxFQUFFLEtBQUssQ0FBQztBQUFBLEVBQ2xEO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxPQUFzQjtBQUNsQixXQUFPLEtBQUssU0FBUyxFQUFFLFVBQVU7QUFBQSxFQUNyQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQU9BLE9BQXNCO0FBQ2xCLFdBQU8sS0FBSyxTQUFTLEVBQUUsVUFBVTtBQUFBLEVBQ3JDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxtQkFBa0M7QUFDOUIsV0FBTyxLQUFLLFNBQVMsRUFBRSxzQkFBc0I7QUFBQSxFQUNqRDtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsaUJBQWdDO0FBQzVCLFdBQU8sS0FBSyxTQUFTLEVBQUUsb0JBQW9CO0FBQUEsRUFDL0M7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGtCQUFpQztBQUM3QixXQUFPLEtBQUssU0FBUyxFQUFFLHFCQUFxQjtBQUFBLEVBQ2hEO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxlQUE4QjtBQUMxQixXQUFPLEtBQUssU0FBUyxFQUFFLGtCQUFrQjtBQUFBLEVBQzdDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxhQUE0QjtBQUN4QixXQUFPLEtBQUssU0FBUyxFQUFFLGdCQUFnQjtBQUFBLEVBQzNDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxhQUE0QjtBQUN4QixXQUFPLEtBQUssU0FBUyxFQUFFLGdCQUFnQjtBQUFBLEVBQzNDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBT0EsUUFBeUI7QUFDckIsV0FBTyxLQUFLLFNBQVMsRUFBRSxXQUFXO0FBQUEsRUFDdEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLE9BQXNCO0FBQ2xCLFdBQU8sS0FBSyxTQUFTLEVBQUUsVUFBVTtBQUFBLEVBQ3JDO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxTQUF3QjtBQUNwQixXQUFPLEtBQUssU0FBUyxFQUFFLFlBQVk7QUFBQSxFQUN2QztBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS0EsVUFBeUI7QUFDckIsV0FBTyxLQUFLLFNBQVMsRUFBRSxhQUFhO0FBQUEsRUFDeEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFlBQTJCO0FBQ3ZCLFdBQU8sS0FBSyxTQUFTLEVBQUUsZUFBZTtBQUFBLEVBQzFDO0FBQ0o7QUFsYkEsSUFBTSxTQUFOO0FBdWJBLElBQU0sYUFBYSxJQUFJLE9BQU8sRUFBRTtBQUVoQyxJQUFPLGlCQUFROzs7QVR6ZmYsU0FBUyxVQUFVLFdBQW1CLE9BQVksTUFBWTtBQUMxRCxPQUFLLEtBQUssV0FBVyxJQUFJO0FBQzdCO0FBUUEsU0FBUyxpQkFBaUIsWUFBb0IsWUFBb0I7QUFDOUQsUUFBTSxlQUFlLGVBQU8sSUFBSSxVQUFVO0FBQzFDLFFBQU0sU0FBVSxhQUFxQixVQUFVO0FBRS9DLE1BQUksT0FBTyxXQUFXLFlBQVk7QUFDOUIsWUFBUSxNQUFNLGtCQUFrQixtQkFBVSxjQUFhO0FBQ3ZEO0FBQUEsRUFDSjtBQUVBLE1BQUk7QUFDQSxXQUFPLEtBQUssWUFBWTtBQUFBLEVBQzVCLFNBQVMsR0FBRztBQUNSLFlBQVEsTUFBTSxnQ0FBZ0MsbUJBQVUsUUFBTyxDQUFDO0FBQUEsRUFDcEU7QUFDSjtBQUtBLFNBQVMsZUFBZSxJQUFpQjtBQUNyQyxRQUFNLFVBQVUsR0FBRztBQUVuQixXQUFTLFVBQVUsU0FBUyxPQUFPO0FBQy9CLFFBQUksV0FBVztBQUNYO0FBRUosVUFBTSxZQUFZLFFBQVEsYUFBYSxXQUFXLEtBQUssUUFBUSxhQUFhLGdCQUFnQjtBQUM1RixVQUFNLGVBQWUsUUFBUSxhQUFhLG1CQUFtQixLQUFLLFFBQVEsYUFBYSx3QkFBd0IsS0FBSztBQUNwSCxVQUFNLGVBQWUsUUFBUSxhQUFhLFlBQVksS0FBSyxRQUFRLGFBQWEsaUJBQWlCO0FBQ2pHLFVBQU0sTUFBTSxRQUFRLGFBQWEsYUFBYSxLQUFLLFFBQVEsYUFBYSxrQkFBa0I7QUFFMUYsUUFBSSxjQUFjO0FBQ2QsZ0JBQVUsU0FBUztBQUN2QixRQUFJLGlCQUFpQjtBQUNqQix1QkFBaUIsY0FBYyxZQUFZO0FBQy9DLFFBQUksUUFBUTtBQUNSLFdBQUssUUFBUSxHQUFHO0FBQUEsRUFDeEI7QUFFQSxRQUFNLFVBQVUsUUFBUSxhQUFhLGFBQWEsS0FBSyxRQUFRLGFBQWEsa0JBQWtCO0FBRTlGLE1BQUksU0FBUztBQUNULGFBQVM7QUFBQSxNQUNMLE9BQU87QUFBQSxNQUNQLFNBQVM7QUFBQSxNQUNULFVBQVU7QUFBQSxNQUNWLFNBQVM7QUFBQSxRQUNMLEVBQUUsT0FBTyxNQUFNO0FBQUEsUUFDZixFQUFFLE9BQU8sTUFBTSxXQUFXLEtBQUs7QUFBQSxNQUNuQztBQUFBLElBQ0osQ0FBQyxFQUFFLEtBQUssU0FBUztBQUFBLEVBQ3JCLE9BQU87QUFDSCxjQUFVO0FBQUEsRUFDZDtBQUNKO0FBR0EsSUFBTSxnQkFBZ0IsT0FBTyxZQUFZO0FBQ3pDLElBQU0sZ0JBQWdCLE9BQU8sWUFBWTtBQUN6QyxJQUFNLGtCQUFrQixPQUFPLGNBQWM7QUFReEM7QUFGTCxJQUFNLDBCQUFOLE1BQThCO0FBQUEsRUFJMUIsY0FBYztBQUNWLFNBQUssYUFBYSxJQUFJLElBQUksZ0JBQWdCO0FBQUEsRUFDOUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBU0EsSUFBSSxTQUFrQixVQUE2QztBQUMvRCxXQUFPLEVBQUUsUUFBUSxLQUFLLGFBQWEsRUFBRSxPQUFPO0FBQUEsRUFDaEQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFFBQWM7QUFDVixTQUFLLGFBQWEsRUFBRSxNQUFNO0FBQzFCLFNBQUssYUFBYSxJQUFJLElBQUksZ0JBQWdCO0FBQUEsRUFDOUM7QUFDSjtBQVNLLGVBRUE7QUFKTCxJQUFNLGtCQUFOLE1BQXNCO0FBQUEsRUFNbEIsY0FBYztBQUNWLFNBQUssYUFBYSxJQUFJLG9CQUFJLFFBQVE7QUFDbEMsU0FBSyxlQUFlLElBQUk7QUFBQSxFQUM1QjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBUUEsSUFBSSxTQUFrQixVQUE2QztBQUMvRCxRQUFJLENBQUMsS0FBSyxhQUFhLEVBQUUsSUFBSSxPQUFPLEdBQUc7QUFBRSxXQUFLLGVBQWU7QUFBQSxJQUFLO0FBQ2xFLFNBQUssYUFBYSxFQUFFLElBQUksU0FBUyxRQUFRO0FBQ3pDLFdBQU8sQ0FBQztBQUFBLEVBQ1o7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLFFBQWM7QUFDVixRQUFJLEtBQUssZUFBZSxLQUFLO0FBQ3pCO0FBRUosZUFBVyxXQUFXLFNBQVMsS0FBSyxpQkFBaUIsR0FBRyxHQUFHO0FBQ3ZELFVBQUksS0FBSyxlQUFlLEtBQUs7QUFDekI7QUFFSixZQUFNLFdBQVcsS0FBSyxhQUFhLEVBQUUsSUFBSSxPQUFPO0FBQ2hELFVBQUksWUFBWSxNQUFNO0FBQUUsYUFBSyxlQUFlO0FBQUEsTUFBSztBQUVqRCxpQkFBVyxXQUFXLFlBQVksQ0FBQztBQUMvQixnQkFBUSxvQkFBb0IsU0FBUyxjQUFjO0FBQUEsSUFDM0Q7QUFFQSxTQUFLLGFBQWEsSUFBSSxvQkFBSSxRQUFRO0FBQ2xDLFNBQUssZUFBZSxJQUFJO0FBQUEsRUFDNUI7QUFDSjtBQUVBLElBQU0sa0JBQWtCLGtCQUFrQixJQUFJLElBQUksd0JBQXdCLElBQUksSUFBSSxnQkFBZ0I7QUFLbEcsU0FBUyxnQkFBZ0IsU0FBd0I7QUFDN0MsUUFBTSxnQkFBZ0I7QUFDdEIsUUFBTSxjQUFlLFFBQVEsYUFBYSxhQUFhLEtBQUssUUFBUSxhQUFhLGtCQUFrQixLQUFLO0FBQ3hHLFFBQU0sV0FBcUIsQ0FBQztBQUU1QixNQUFJO0FBQ0osVUFBUSxRQUFRLGNBQWMsS0FBSyxXQUFXLE9BQU87QUFDakQsYUFBUyxLQUFLLE1BQU0sQ0FBQyxDQUFDO0FBRTFCLFFBQU0sVUFBVSxnQkFBZ0IsSUFBSSxTQUFTLFFBQVE7QUFDckQsYUFBVyxXQUFXO0FBQ2xCLFlBQVEsaUJBQWlCLFNBQVMsZ0JBQWdCLE9BQU87QUFDakU7QUFLTyxTQUFTLFNBQWU7QUFDM0IsWUFBVSxNQUFNO0FBQ3BCO0FBS08sU0FBUyxTQUFlO0FBQzNCLGtCQUFnQixNQUFNO0FBQ3RCLFdBQVMsS0FBSyxpQkFBaUIsbUdBQW1HLEVBQUUsUUFBUSxlQUFlO0FBQy9KOzs7QVVoTUEsT0FBTyxRQUFRO0FBQ2YsT0FBVTtBQUVWLElBQUksTUFBTztBQUNQLFdBQVMsc0JBQXNCO0FBQ25DOzs7QUNyQkE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVlBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksTUFBTTtBQUVoRCxJQUFNLG1CQUFtQjtBQUN6QixJQUFNLG9CQUFvQjtBQUUxQixJQUFNLFVBQVcsV0FBWTtBQWpCN0IsTUFBQUMsS0FBQTtBQWtCSSxNQUFJO0FBQ0EsU0FBSyxNQUFBQSxNQUFBLE9BQWUsV0FBZixnQkFBQUEsSUFBdUIsWUFBdkIsbUJBQWdDLGFBQWE7QUFDOUMsYUFBUSxPQUFlLE9BQU8sUUFBUSxZQUFZLEtBQU0sT0FBZSxPQUFPLE9BQU87QUFBQSxJQUN6RixZQUFZLHdCQUFlLFdBQWYsbUJBQXVCLG9CQUF2QixtQkFBeUMsZ0JBQXpDLG1CQUFzRCxhQUFhO0FBQzNFLGFBQVEsT0FBZSxPQUFPLGdCQUFnQixVQUFVLEVBQUUsWUFBWSxLQUFNLE9BQWUsT0FBTyxnQkFBZ0IsVUFBVSxDQUFDO0FBQUEsSUFDakk7QUFBQSxFQUNKLFNBQVEsR0FBRztBQUFBLEVBQUM7QUFFWixVQUFRO0FBQUEsSUFBSztBQUFBLElBQ1Q7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQXdEO0FBQzVELFNBQU87QUFDWCxFQUFHO0FBRUksU0FBUyxPQUFPLEtBQWdCO0FBQ25DLHFDQUFVO0FBQ2Q7QUFPTyxTQUFTLGFBQStCO0FBQzNDLFNBQU9ELE1BQUssZ0JBQWdCO0FBQ2hDO0FBT0EsZUFBc0IsZUFBNkM7QUFDL0QsTUFBSSxXQUFXLE1BQU0sTUFBTSxxQkFBcUI7QUFDaEQsTUFBSSxTQUFTLElBQUk7QUFDYixXQUFPLFNBQVMsS0FBSztBQUFBLEVBQ3pCLE9BQU87QUFDSCxVQUFNLElBQUksTUFBTSxtQ0FBbUMsU0FBUyxVQUFVO0FBQUEsRUFDMUU7QUFDSjtBQStCTyxTQUFTLGNBQXdDO0FBQ3BELFNBQU9BLE1BQUssaUJBQWlCO0FBQ2pDO0FBT08sU0FBUyxZQUFxQjtBQUNqQyxTQUFPLE9BQU8sT0FBTyxZQUFZLE9BQU87QUFDNUM7QUFPTyxTQUFTLFVBQW1CO0FBQy9CLFNBQU8sT0FBTyxPQUFPLFlBQVksT0FBTztBQUM1QztBQU9PLFNBQVMsUUFBaUI7QUFDN0IsU0FBTyxPQUFPLE9BQU8sWUFBWSxPQUFPO0FBQzVDO0FBT08sU0FBUyxVQUFtQjtBQUMvQixTQUFPLE9BQU8sT0FBTyxZQUFZLFNBQVM7QUFDOUM7QUFPTyxTQUFTLFFBQWlCO0FBQzdCLFNBQU8sT0FBTyxPQUFPLFlBQVksU0FBUztBQUM5QztBQU9PLFNBQVMsVUFBbUI7QUFDL0IsU0FBTyxPQUFPLE9BQU8sWUFBWSxTQUFTO0FBQzlDO0FBT08sU0FBUyxVQUFtQjtBQUMvQixTQUFPLFFBQVEsT0FBTyxPQUFPLFlBQVksS0FBSztBQUNsRDs7O0FDM0lBLE9BQU8saUJBQWlCLGVBQWUsa0JBQWtCO0FBRXpELElBQU1FLFFBQU8saUJBQWlCLFlBQVksV0FBVztBQUVyRCxJQUFNLGtCQUFrQjtBQUV4QixTQUFTLGdCQUFnQixJQUFZLEdBQVcsR0FBVyxNQUFpQjtBQUN4RSxPQUFLQSxNQUFLLGlCQUFpQixFQUFDLElBQUksR0FBRyxHQUFHLEtBQUksQ0FBQztBQUMvQztBQUVBLFNBQVMsbUJBQW1CLE9BQW1CO0FBQzNDLFFBQU0sU0FBUyxZQUFZLEtBQUs7QUFHaEMsUUFBTSxvQkFBb0IsT0FBTyxpQkFBaUIsTUFBTSxFQUFFLGlCQUFpQixzQkFBc0IsRUFBRSxLQUFLO0FBRXhHLE1BQUksbUJBQW1CO0FBQ25CLFVBQU0sZUFBZTtBQUNyQixVQUFNLE9BQU8sT0FBTyxpQkFBaUIsTUFBTSxFQUFFLGlCQUFpQiwyQkFBMkI7QUFDekYsb0JBQWdCLG1CQUFtQixNQUFNLFNBQVMsTUFBTSxTQUFTLElBQUk7QUFBQSxFQUN6RSxPQUFPO0FBQ0gsOEJBQTBCLE9BQU8sTUFBTTtBQUFBLEVBQzNDO0FBQ0o7QUFVQSxTQUFTLDBCQUEwQixPQUFtQixRQUFxQjtBQUV2RSxNQUFJLFFBQVEsR0FBRztBQUNYO0FBQUEsRUFDSjtBQUdBLFVBQVEsT0FBTyxpQkFBaUIsTUFBTSxFQUFFLGlCQUFpQix1QkFBdUIsRUFBRSxLQUFLLEdBQUc7QUFBQSxJQUN0RixLQUFLO0FBQ0Q7QUFBQSxJQUNKLEtBQUs7QUFDRCxZQUFNLGVBQWU7QUFDckI7QUFBQSxFQUNSO0FBR0EsTUFBSSxPQUFPLG1CQUFtQjtBQUMxQjtBQUFBLEVBQ0o7QUFHQSxRQUFNLFlBQVksT0FBTyxhQUFhO0FBQ3RDLFFBQU0sZUFBZSxhQUFhLFVBQVUsU0FBUyxFQUFFLFNBQVM7QUFDaEUsTUFBSSxjQUFjO0FBQ2QsYUFBUyxJQUFJLEdBQUcsSUFBSSxVQUFVLFlBQVksS0FBSztBQUMzQyxZQUFNLFFBQVEsVUFBVSxXQUFXLENBQUM7QUFDcEMsWUFBTSxRQUFRLE1BQU0sZUFBZTtBQUNuQyxlQUFTLElBQUksR0FBRyxJQUFJLE1BQU0sUUFBUSxLQUFLO0FBQ25DLGNBQU0sT0FBTyxNQUFNLENBQUM7QUFDcEIsWUFBSSxTQUFTLGlCQUFpQixLQUFLLE1BQU0sS0FBSyxHQUFHLE1BQU0sUUFBUTtBQUMzRDtBQUFBLFFBQ0o7QUFBQSxNQUNKO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFHQSxNQUFJLGtCQUFrQixvQkFBb0Isa0JBQWtCLHFCQUFxQjtBQUM3RSxRQUFJLGdCQUFpQixDQUFDLE9BQU8sWUFBWSxDQUFDLE9BQU8sVUFBVztBQUN4RDtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBR0EsUUFBTSxlQUFlO0FBQ3pCOzs7QUM3RkE7QUFBQTtBQUFBO0FBQUE7QUFnQk8sU0FBUyxRQUFRLEtBQWtCO0FBQ3RDLE1BQUk7QUFDQSxXQUFPLE9BQU8sT0FBTyxNQUFNLEdBQUc7QUFBQSxFQUNsQyxTQUFTLEdBQUc7QUFDUixVQUFNLElBQUksTUFBTSw4QkFBOEIsTUFBTSxRQUFRLEdBQUcsRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUFBLEVBQy9FO0FBQ0o7OztBQ1BBLElBQUksVUFBVTtBQUNkLElBQUksV0FBVztBQUVmLElBQUksWUFBWTtBQUNoQixJQUFJLFlBQVk7QUFDaEIsSUFBSSxXQUFXO0FBQ2YsSUFBSSxhQUFxQjtBQUN6QixJQUFJLGdCQUFnQjtBQUVwQixJQUFJLFVBQVU7QUFDZCxJQUFNLGlCQUFpQixnQkFBZ0I7QUFFdkMsT0FBTyxTQUFTLE9BQU8sVUFBVSxDQUFDO0FBQ2xDLE9BQU8sT0FBTyxlQUFlLENBQUMsVUFBeUI7QUFDbkQsY0FBWTtBQUNaLE1BQUksQ0FBQyxXQUFXO0FBRVosZ0JBQVksV0FBVztBQUN2QixjQUFVO0FBQUEsRUFDZDtBQUNKO0FBRUEsT0FBTyxpQkFBaUIsYUFBYSxRQUFRLEVBQUUsU0FBUyxLQUFLLENBQUM7QUFDOUQsT0FBTyxpQkFBaUIsYUFBYSxRQUFRLEVBQUUsU0FBUyxLQUFLLENBQUM7QUFDOUQsT0FBTyxpQkFBaUIsV0FBVyxRQUFRLEVBQUUsU0FBUyxLQUFLLENBQUM7QUFDNUQsV0FBVyxNQUFNLENBQUMsU0FBUyxlQUFlLFVBQVUsR0FBRztBQUNuRCxTQUFPLGlCQUFpQixJQUFJLGVBQWUsRUFBRSxTQUFTLEtBQUssQ0FBQztBQUNoRTtBQUVBLFNBQVMsY0FBYyxPQUFjO0FBRWpDLE1BQUksWUFBWSxVQUFVO0FBQ3RCLFVBQU0seUJBQXlCO0FBQy9CLFVBQU0sZ0JBQWdCO0FBQ3RCLFVBQU0sZUFBZTtBQUFBLEVBQ3pCO0FBQ0o7QUFHQSxJQUFNLFlBQVk7QUFDbEIsSUFBTSxVQUFZO0FBQ2xCLElBQU0sWUFBWTtBQUVsQixTQUFTLE9BQU8sT0FBbUI7QUFJL0IsTUFBSSxXQUFtQixlQUFlLE1BQU07QUFDNUMsVUFBUSxNQUFNLE1BQU07QUFBQSxJQUNoQixLQUFLO0FBQ0Qsa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWUsVUFBVyxLQUFLLE1BQU07QUFBQSxNQUFTO0FBQ3JFO0FBQUEsSUFDSixLQUFLO0FBQ0Qsa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWUsVUFBVSxFQUFFLEtBQUssTUFBTTtBQUFBLE1BQVM7QUFDdEU7QUFBQSxJQUNKO0FBQ0ksa0JBQVk7QUFDWixVQUFJLENBQUMsZ0JBQWdCO0FBQUUsdUJBQWU7QUFBQSxNQUFTO0FBQy9DO0FBQUEsRUFDUjtBQUVBLE1BQUksV0FBVyxVQUFVLENBQUM7QUFDMUIsTUFBSSxVQUFVLGVBQWUsQ0FBQztBQUU5QixZQUFVO0FBR1YsTUFBSSxjQUFjLGFBQWEsRUFBRSxVQUFVLE1BQU0sU0FBUztBQUN0RCxnQkFBYSxLQUFLLE1BQU07QUFDeEIsZUFBWSxLQUFLLE1BQU07QUFBQSxFQUMzQjtBQUlBLE1BQ0ksY0FBYyxhQUNYLFlBRUMsYUFFSSxjQUFjLGFBQ1gsTUFBTSxXQUFXLElBRzlCO0FBQ0UsVUFBTSx5QkFBeUI7QUFDL0IsVUFBTSxnQkFBZ0I7QUFDdEIsVUFBTSxlQUFlO0FBQUEsRUFDekI7QUFHQSxNQUFJLFdBQVcsR0FBRztBQUFFLGNBQVUsS0FBSztBQUFBLEVBQUc7QUFFdEMsTUFBSSxVQUFVLEdBQUc7QUFBRSxnQkFBWSxLQUFLO0FBQUEsRUFBRztBQUd2QyxNQUFJLGNBQWMsV0FBVztBQUFFLGdCQUFZLEtBQUs7QUFBQSxFQUFHO0FBQUM7QUFDeEQ7QUFFQSxTQUFTLFlBQVksT0FBeUI7QUFFMUMsWUFBVTtBQUNWLGNBQVk7QUFHWixNQUFJLENBQUMsVUFBVSxHQUFHO0FBQ2QsUUFBSSxNQUFNLFNBQVMsZUFBZSxNQUFNLFdBQVcsS0FBSyxNQUFNLFdBQVcsR0FBRztBQUN4RTtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBRUEsTUFBSSxZQUFZO0FBRVosZ0JBQVk7QUFFWjtBQUFBLEVBQ0o7QUFHQSxRQUFNLFNBQVMsWUFBWSxLQUFLO0FBSWhDLFFBQU0sUUFBUSxPQUFPLGlCQUFpQixNQUFNO0FBQzVDLFlBQ0ksTUFBTSxpQkFBaUIsbUJBQW1CLEVBQUUsS0FBSyxNQUFNLFdBRW5ELE1BQU0sVUFBVSxXQUFXLE1BQU0sV0FBVyxJQUFJLE9BQU8sZUFDcEQsTUFBTSxVQUFVLFdBQVcsTUFBTSxVQUFVLElBQUksT0FBTztBQUdyRTtBQUVBLFNBQVMsVUFBVSxPQUFtQjtBQUVsQyxZQUFVO0FBQ1YsYUFBVztBQUNYLGNBQVk7QUFDWixhQUFXO0FBQ2Y7QUFFQSxJQUFNLGdCQUFnQixPQUFPLE9BQU87QUFBQSxFQUNoQyxhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixhQUFhO0FBQUEsRUFDYixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQUEsRUFDWixZQUFZO0FBQ2hCLENBQUM7QUFFRCxTQUFTLFVBQVUsTUFBeUM7QUFDeEQsTUFBSSxNQUFNO0FBQ04sUUFBSSxDQUFDLFlBQVk7QUFBRSxzQkFBZ0IsU0FBUyxLQUFLLE1BQU07QUFBQSxJQUFRO0FBQy9ELGFBQVMsS0FBSyxNQUFNLFNBQVMsY0FBYyxJQUFJO0FBQUEsRUFDbkQsV0FBVyxDQUFDLFFBQVEsWUFBWTtBQUM1QixhQUFTLEtBQUssTUFBTSxTQUFTO0FBQUEsRUFDakM7QUFFQSxlQUFhLFFBQVE7QUFDekI7QUFFQSxTQUFTLFlBQVksT0FBeUI7QUFDMUMsTUFBSSxhQUFhLFlBQVk7QUFFekIsZUFBVztBQUNYLFdBQU8sa0JBQWtCLFVBQVU7QUFBQSxFQUN2QyxXQUFXLFNBQVM7QUFFaEIsZUFBVztBQUNYLFdBQU8sWUFBWTtBQUFBLEVBQ3ZCO0FBRUEsTUFBSSxZQUFZLFVBQVU7QUFHdEIsY0FBVSxZQUFZO0FBQ3RCO0FBQUEsRUFDSjtBQUVBLE1BQUksQ0FBQyxhQUFhLENBQUMsVUFBVSxHQUFHO0FBQzVCLFFBQUksWUFBWTtBQUFFLGdCQUFVO0FBQUEsSUFBRztBQUMvQjtBQUFBLEVBQ0o7QUFFQSxRQUFNLHFCQUFxQixRQUFRLDJCQUEyQixLQUFLO0FBQ25FLFFBQU0sb0JBQW9CLFFBQVEsMEJBQTBCLEtBQUs7QUFHakUsUUFBTSxjQUFjLFFBQVEsbUJBQW1CLEtBQUs7QUFFcEQsUUFBTSxjQUFlLE9BQU8sYUFBYSxNQUFNLFVBQVc7QUFDMUQsUUFBTSxhQUFhLE1BQU0sVUFBVTtBQUNuQyxRQUFNLFlBQVksTUFBTSxVQUFVO0FBQ2xDLFFBQU0sZUFBZ0IsT0FBTyxjQUFjLE1BQU0sVUFBVztBQUc1RCxRQUFNLGNBQWUsT0FBTyxhQUFhLE1BQU0sVUFBWSxvQkFBb0I7QUFDL0UsUUFBTSxhQUFhLE1BQU0sVUFBVyxvQkFBb0I7QUFDeEQsUUFBTSxZQUFZLE1BQU0sVUFBVyxxQkFBcUI7QUFDeEQsUUFBTSxlQUFnQixPQUFPLGNBQWMsTUFBTSxVQUFZLHFCQUFxQjtBQUVsRixNQUFJLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhO0FBRTVELGNBQVU7QUFBQSxFQUNkLFdBRVMsZUFBZSxhQUFjLFdBQVUsV0FBVztBQUFBLFdBQ2xELGNBQWMsYUFBYyxXQUFVLFdBQVc7QUFBQSxXQUNqRCxjQUFjLFVBQVcsV0FBVSxXQUFXO0FBQUEsV0FDOUMsYUFBYSxZQUFhLFdBQVUsV0FBVztBQUFBLFdBRS9DLFdBQVksV0FBVSxVQUFVO0FBQUEsV0FDaEMsVUFBVyxXQUFVLFVBQVU7QUFBQSxXQUMvQixhQUFjLFdBQVUsVUFBVTtBQUFBLFdBQ2xDLFlBQWEsV0FBVSxVQUFVO0FBQUEsTUFFckMsV0FBVTtBQUNuQjs7O0FDNU9BO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQVdBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksV0FBVztBQUVyRCxJQUFNQyxjQUFhO0FBQ25CLElBQU1DLGNBQWE7QUFDbkIsSUFBTSxhQUFhO0FBS1osU0FBUyxPQUFzQjtBQUNsQyxTQUFPRixNQUFLQyxXQUFVO0FBQzFCO0FBS08sU0FBUyxPQUFzQjtBQUNsQyxTQUFPRCxNQUFLRSxXQUFVO0FBQzFCO0FBS08sU0FBUyxPQUFzQjtBQUNsQyxTQUFPRixNQUFLLFVBQVU7QUFDMUI7OztBQ3BDQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTs7O0FDd0JBLElBQUksVUFBVSxTQUFTLFVBQVU7QUFDakMsSUFBSSxlQUFvRCxPQUFPLFlBQVksWUFBWSxZQUFZLFFBQVEsUUFBUTtBQUNuSCxJQUFJO0FBQ0osSUFBSTtBQUNKLElBQUksT0FBTyxpQkFBaUIsY0FBYyxPQUFPLE9BQU8sbUJBQW1CLFlBQVk7QUFDbkYsTUFBSTtBQUNBLG1CQUFlLE9BQU8sZUFBZSxDQUFDLEdBQUcsVUFBVTtBQUFBLE1BQy9DLEtBQUssV0FBWTtBQUNiLGNBQU07QUFBQSxNQUNWO0FBQUEsSUFDSixDQUFDO0FBQ0QsdUJBQW1CLENBQUM7QUFFcEIsaUJBQWEsV0FBWTtBQUFFLFlBQU07QUFBQSxJQUFJLEdBQUcsTUFBTSxZQUFZO0FBQUEsRUFDOUQsU0FBUyxHQUFHO0FBQ1IsUUFBSSxNQUFNLGtCQUFrQjtBQUN4QixxQkFBZTtBQUFBLElBQ25CO0FBQUEsRUFDSjtBQUNKLE9BQU87QUFDSCxpQkFBZTtBQUNuQjtBQUVBLElBQUksbUJBQW1CO0FBQ3ZCLElBQUksZUFBZSxTQUFTLG1CQUFtQixPQUFxQjtBQUNoRSxNQUFJO0FBQ0EsUUFBSSxRQUFRLFFBQVEsS0FBSyxLQUFLO0FBQzlCLFdBQU8saUJBQWlCLEtBQUssS0FBSztBQUFBLEVBQ3RDLFNBQVMsR0FBRztBQUNSLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFFQSxJQUFJLG9CQUFvQixTQUFTLGlCQUFpQixPQUFxQjtBQUNuRSxNQUFJO0FBQ0EsUUFBSSxhQUFhLEtBQUssR0FBRztBQUFFLGFBQU87QUFBQSxJQUFPO0FBQ3pDLFlBQVEsS0FBSyxLQUFLO0FBQ2xCLFdBQU87QUFBQSxFQUNYLFNBQVMsR0FBRztBQUNSLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFDQSxJQUFJLFFBQVEsT0FBTyxVQUFVO0FBQzdCLElBQUksY0FBYztBQUNsQixJQUFJLFVBQVU7QUFDZCxJQUFJLFdBQVc7QUFDZixJQUFJLFdBQVc7QUFDZixJQUFJLFlBQVk7QUFDaEIsSUFBSSxZQUFZO0FBQ2hCLElBQUksaUJBQWlCLE9BQU8sV0FBVyxjQUFjLENBQUMsQ0FBQyxPQUFPO0FBRTlELElBQUksU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRXRCLElBQUksUUFBaUMsU0FBUyxtQkFBbUI7QUFBRSxTQUFPO0FBQU87QUFDakYsSUFBSSxPQUFPLGFBQWEsVUFBVTtBQUUxQixRQUFNLFNBQVM7QUFDbkIsTUFBSSxNQUFNLEtBQUssR0FBRyxNQUFNLE1BQU0sS0FBSyxTQUFTLEdBQUcsR0FBRztBQUM5QyxZQUFRLFNBQVNHLGtCQUFpQixPQUFPO0FBR3JDLFdBQUssVUFBVSxDQUFDLFdBQVcsT0FBTyxVQUFVLGVBQWUsT0FBTyxVQUFVLFdBQVc7QUFDbkYsWUFBSTtBQUNBLGNBQUksTUFBTSxNQUFNLEtBQUssS0FBSztBQUMxQixrQkFDSSxRQUFRLFlBQ0wsUUFBUSxhQUNSLFFBQVEsYUFDUixRQUFRLGdCQUNWLE1BQU0sRUFBRSxLQUFLO0FBQUEsUUFDdEIsU0FBUyxHQUFHO0FBQUEsUUFBTztBQUFBLE1BQ3ZCO0FBQ0EsYUFBTztBQUFBLElBQ1g7QUFBQSxFQUNKO0FBQ0o7QUFuQlE7QUFxQlIsU0FBUyxtQkFBc0IsT0FBdUQ7QUFDbEYsTUFBSSxNQUFNLEtBQUssR0FBRztBQUFFLFdBQU87QUFBQSxFQUFNO0FBQ2pDLE1BQUksQ0FBQyxPQUFPO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDNUIsTUFBSSxPQUFPLFVBQVUsY0FBYyxPQUFPLFVBQVUsVUFBVTtBQUFFLFdBQU87QUFBQSxFQUFPO0FBQzlFLE1BQUk7QUFDQSxJQUFDLGFBQXFCLE9BQU8sTUFBTSxZQUFZO0FBQUEsRUFDbkQsU0FBUyxHQUFHO0FBQ1IsUUFBSSxNQUFNLGtCQUFrQjtBQUFFLGFBQU87QUFBQSxJQUFPO0FBQUEsRUFDaEQ7QUFDQSxTQUFPLENBQUMsYUFBYSxLQUFLLEtBQUssa0JBQWtCLEtBQUs7QUFDMUQ7QUFFQSxTQUFTLHFCQUF3QixPQUFzRDtBQUNuRixNQUFJLE1BQU0sS0FBSyxHQUFHO0FBQUUsV0FBTztBQUFBLEVBQU07QUFDakMsTUFBSSxDQUFDLE9BQU87QUFBRSxXQUFPO0FBQUEsRUFBTztBQUM1QixNQUFJLE9BQU8sVUFBVSxjQUFjLE9BQU8sVUFBVSxVQUFVO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDOUUsTUFBSSxnQkFBZ0I7QUFBRSxXQUFPLGtCQUFrQixLQUFLO0FBQUEsRUFBRztBQUN2RCxNQUFJLGFBQWEsS0FBSyxHQUFHO0FBQUUsV0FBTztBQUFBLEVBQU87QUFDekMsTUFBSSxXQUFXLE1BQU0sS0FBSyxLQUFLO0FBQy9CLE1BQUksYUFBYSxXQUFXLGFBQWEsWUFBWSxDQUFFLGlCQUFrQixLQUFLLFFBQVEsR0FBRztBQUFFLFdBQU87QUFBQSxFQUFPO0FBQ3pHLFNBQU8sa0JBQWtCLEtBQUs7QUFDbEM7QUFFQSxJQUFPLG1CQUFRLGVBQWUscUJBQXFCOzs7QUN6RzVDLElBQU0sY0FBTixjQUEwQixNQUFNO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBTW5DLFlBQVksU0FBa0IsU0FBd0I7QUFDbEQsVUFBTSxTQUFTLE9BQU87QUFDdEIsU0FBSyxPQUFPO0FBQUEsRUFDaEI7QUFDSjtBQWNPLElBQU0sMEJBQU4sY0FBc0MsTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFhL0MsWUFBWSxTQUFzQyxRQUFjLE1BQWU7QUFDM0UsV0FBTyxzQkFBUSwrQ0FBK0MsY0FBYyxhQUFhLE1BQU0sR0FBRyxFQUFFLE9BQU8sT0FBTyxDQUFDO0FBQ25ILFNBQUssVUFBVTtBQUNmLFNBQUssT0FBTztBQUFBLEVBQ2hCO0FBQ0o7QUErQkEsSUFBTSxhQUFhLE9BQU8sU0FBUztBQUNuQyxJQUFNLGdCQUFnQixPQUFPLFlBQVk7QUE3RnpDO0FBOEZBLElBQU0sV0FBVSxZQUFPLFlBQVAsWUFBa0IsT0FBTyxpQkFBaUI7QUFvRG5ELElBQU0scUJBQU4sTUFBTSw0QkFBOEIsUUFBZ0U7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUF1Q3ZHLFlBQVksVUFBeUMsYUFBMkM7QUFDNUYsUUFBSTtBQUNKLFFBQUk7QUFDSixVQUFNLENBQUMsS0FBSyxRQUFRO0FBQUUsZ0JBQVU7QUFBSyxlQUFTO0FBQUEsSUFBSyxDQUFDO0FBRXBELFFBQUssS0FBSyxZQUFvQixPQUFPLE1BQU0sU0FBUztBQUNoRCxZQUFNLElBQUksVUFBVSxtSUFBbUk7QUFBQSxJQUMzSjtBQUVBLFFBQUksVUFBOEM7QUFBQSxNQUM5QyxTQUFTO0FBQUEsTUFDVDtBQUFBLE1BQ0E7QUFBQSxNQUNBLElBQUksY0FBYztBQUFFLGVBQU8sb0NBQWU7QUFBQSxNQUFNO0FBQUEsTUFDaEQsSUFBSSxZQUFZLElBQUk7QUFBRSxzQkFBYyxrQkFBTTtBQUFBLE1BQVc7QUFBQSxJQUN6RDtBQUVBLFVBQU0sUUFBaUM7QUFBQSxNQUNuQyxJQUFJLE9BQU87QUFBRSxlQUFPO0FBQUEsTUFBTztBQUFBLE1BQzNCLFdBQVc7QUFBQSxNQUNYLFNBQVM7QUFBQSxJQUNiO0FBR0EsU0FBSyxPQUFPLGlCQUFpQixNQUFNO0FBQUEsTUFDL0IsQ0FBQyxVQUFVLEdBQUc7QUFBQSxRQUNWLGNBQWM7QUFBQSxRQUNkLFlBQVk7QUFBQSxRQUNaLFVBQVU7QUFBQSxRQUNWLE9BQU87QUFBQSxNQUNYO0FBQUEsTUFDQSxDQUFDLGFBQWEsR0FBRztBQUFBLFFBQ2IsY0FBYztBQUFBLFFBQ2QsWUFBWTtBQUFBLFFBQ1osVUFBVTtBQUFBLFFBQ1YsT0FBTyxhQUFhLFNBQVMsS0FBSztBQUFBLE1BQ3RDO0FBQUEsSUFDSixDQUFDO0FBR0QsVUFBTSxXQUFXLFlBQVksU0FBUyxLQUFLO0FBQzNDLFFBQUk7QUFDQSxlQUFTLFlBQVksU0FBUyxLQUFLLEdBQUcsUUFBUTtBQUFBLElBQ2xELFNBQVMsS0FBSztBQUNWLFVBQUksTUFBTSxXQUFXO0FBQ2pCLGdCQUFRLElBQUksdURBQXVELEdBQUc7QUFBQSxNQUMxRSxPQUFPO0FBQ0gsaUJBQVMsR0FBRztBQUFBLE1BQ2hCO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBeURBLE9BQU8sT0FBdUM7QUFDMUMsV0FBTyxJQUFJLG9CQUF5QixDQUFDLFlBQVk7QUFHN0MsY0FBUSxJQUFJO0FBQUEsUUFDUixLQUFLLGFBQWEsRUFBRSxJQUFJLFlBQVksc0JBQXNCLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFBQSxRQUNwRSxlQUFlLElBQUk7QUFBQSxNQUN2QixDQUFDLEVBQUUsS0FBSyxNQUFNLFFBQVEsR0FBRyxNQUFNLFFBQVEsQ0FBQztBQUFBLElBQzVDLENBQUM7QUFBQSxFQUNMO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQTJCQSxTQUFTLFFBQTRDO0FBQ2pELFFBQUksT0FBTyxTQUFTO0FBQ2hCLFdBQUssS0FBSyxPQUFPLE9BQU8sTUFBTTtBQUFBLElBQ2xDLE9BQU87QUFDSCxhQUFPLGlCQUFpQixTQUFTLE1BQU0sS0FBSyxLQUFLLE9BQU8sT0FBTyxNQUFNLEdBQUcsRUFBQyxTQUFTLEtBQUksQ0FBQztBQUFBLElBQzNGO0FBRUEsV0FBTztBQUFBLEVBQ1g7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUErQkEsS0FBcUMsYUFBc0gsWUFBd0gsYUFBb0Y7QUFDblcsUUFBSSxFQUFFLGdCQUFnQixzQkFBcUI7QUFDdkMsWUFBTSxJQUFJLFVBQVUsZ0VBQWdFO0FBQUEsSUFDeEY7QUFNQSxRQUFJLENBQUMsaUJBQVcsV0FBVyxHQUFHO0FBQUUsb0JBQWM7QUFBQSxJQUFpQjtBQUMvRCxRQUFJLENBQUMsaUJBQVcsVUFBVSxHQUFHO0FBQUUsbUJBQWE7QUFBQSxJQUFTO0FBRXJELFFBQUksZ0JBQWdCLFlBQVksY0FBYyxTQUFTO0FBRW5ELGFBQU8sSUFBSSxvQkFBbUIsQ0FBQyxZQUFZLFFBQVEsSUFBVyxDQUFDO0FBQUEsSUFDbkU7QUFFQSxVQUFNLFVBQStDLENBQUM7QUFDdEQsU0FBSyxVQUFVLElBQUk7QUFFbkIsV0FBTyxJQUFJLG9CQUF3QyxDQUFDLFNBQVMsV0FBVztBQUNwRSxXQUFLLE1BQU07QUFBQSxRQUNQLENBQUMsVUFBVTtBQXJZM0IsY0FBQUM7QUFzWW9CLGNBQUksS0FBSyxVQUFVLE1BQU0sU0FBUztBQUFFLGlCQUFLLFVBQVUsSUFBSTtBQUFBLFVBQU07QUFDN0QsV0FBQUEsTUFBQSxRQUFRLFlBQVIsZ0JBQUFBLElBQUE7QUFFQSxjQUFJO0FBQ0Esb0JBQVEsWUFBYSxLQUFLLENBQUM7QUFBQSxVQUMvQixTQUFTLEtBQUs7QUFDVixtQkFBTyxHQUFHO0FBQUEsVUFDZDtBQUFBLFFBQ0o7QUFBQSxRQUNBLENBQUMsV0FBWTtBQS9ZN0IsY0FBQUE7QUFnWm9CLGNBQUksS0FBSyxVQUFVLE1BQU0sU0FBUztBQUFFLGlCQUFLLFVBQVUsSUFBSTtBQUFBLFVBQU07QUFDN0QsV0FBQUEsTUFBQSxRQUFRLFlBQVIsZ0JBQUFBLElBQUE7QUFFQSxjQUFJO0FBQ0Esb0JBQVEsV0FBWSxNQUFNLENBQUM7QUFBQSxVQUMvQixTQUFTLEtBQUs7QUFDVixtQkFBTyxHQUFHO0FBQUEsVUFDZDtBQUFBLFFBQ0o7QUFBQSxNQUNKO0FBQUEsSUFDSixHQUFHLE9BQU8sVUFBVztBQUVqQixVQUFJO0FBQ0EsZUFBTywyQ0FBYztBQUFBLE1BQ3pCLFVBQUU7QUFDRSxjQUFNLEtBQUssT0FBTyxLQUFLO0FBQUEsTUFDM0I7QUFBQSxJQUNKLENBQUM7QUFBQSxFQUNMO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBK0JBLE1BQXVCLFlBQXFGLGFBQTRFO0FBQ3BMLFdBQU8sS0FBSyxLQUFLLFFBQVcsWUFBWSxXQUFXO0FBQUEsRUFDdkQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBaUNBLFFBQVEsV0FBNkMsYUFBa0U7QUFDbkgsUUFBSSxFQUFFLGdCQUFnQixzQkFBcUI7QUFDdkMsWUFBTSxJQUFJLFVBQVUsbUVBQW1FO0FBQUEsSUFDM0Y7QUFFQSxRQUFJLENBQUMsaUJBQVcsU0FBUyxHQUFHO0FBQ3hCLGFBQU8sS0FBSyxLQUFLLFdBQVcsV0FBVyxXQUFXO0FBQUEsSUFDdEQ7QUFFQSxXQUFPLEtBQUs7QUFBQSxNQUNSLENBQUMsVUFBVSxvQkFBbUIsUUFBUSxVQUFVLENBQUMsRUFBRSxLQUFLLE1BQU0sS0FBSztBQUFBLE1BQ25FLENBQUMsV0FBWSxvQkFBbUIsUUFBUSxVQUFVLENBQUMsRUFBRSxLQUFLLE1BQU07QUFBRSxjQUFNO0FBQUEsTUFBUSxDQUFDO0FBQUEsTUFDakY7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFZQSxhQXpXUyxZQUVTLGVBdVdOLFFBQU8sSUFBSTtBQUNuQixXQUFPO0FBQUEsRUFDWDtBQUFBLEVBYUEsT0FBTyxJQUFzRCxRQUF3QztBQUNqRyxRQUFJLFlBQVksTUFBTSxLQUFLLE1BQU07QUFDakMsVUFBTSxVQUFVLFVBQVUsV0FBVyxJQUMvQixvQkFBbUIsUUFBUSxTQUFTLElBQ3BDLElBQUksb0JBQTRCLENBQUMsU0FBUyxXQUFXO0FBQ25ELFdBQUssUUFBUSxJQUFJLFNBQVMsRUFBRSxLQUFLLFNBQVMsTUFBTTtBQUFBLElBQ3BELEdBQUcsQ0FBQyxVQUEwQixVQUFVLFNBQVMsV0FBVyxLQUFLLENBQUM7QUFDdEUsV0FBTztBQUFBLEVBQ1g7QUFBQSxFQWFBLE9BQU8sV0FBNkQsUUFBd0M7QUFDeEcsUUFBSSxZQUFZLE1BQU0sS0FBSyxNQUFNO0FBQ2pDLFVBQU0sVUFBVSxVQUFVLFdBQVcsSUFDL0Isb0JBQW1CLFFBQVEsU0FBUyxJQUNwQyxJQUFJLG9CQUE0QixDQUFDLFNBQVMsV0FBVztBQUNuRCxXQUFLLFFBQVEsV0FBVyxTQUFTLEVBQUUsS0FBSyxTQUFTLE1BQU07QUFBQSxJQUMzRCxHQUFHLENBQUMsVUFBMEIsVUFBVSxTQUFTLFdBQVcsS0FBSyxDQUFDO0FBQ3RFLFdBQU87QUFBQSxFQUNYO0FBQUEsRUFlQSxPQUFPLElBQXNELFFBQXdDO0FBQ2pHLFFBQUksWUFBWSxNQUFNLEtBQUssTUFBTTtBQUNqQyxVQUFNLFVBQVUsVUFBVSxXQUFXLElBQy9CLG9CQUFtQixRQUFRLFNBQVMsSUFDcEMsSUFBSSxvQkFBNEIsQ0FBQyxTQUFTLFdBQVc7QUFDbkQsV0FBSyxRQUFRLElBQUksU0FBUyxFQUFFLEtBQUssU0FBUyxNQUFNO0FBQUEsSUFDcEQsR0FBRyxDQUFDLFVBQTBCLFVBQVUsU0FBUyxXQUFXLEtBQUssQ0FBQztBQUN0RSxXQUFPO0FBQUEsRUFDWDtBQUFBLEVBWUEsT0FBTyxLQUF1RCxRQUF3QztBQUNsRyxRQUFJLFlBQVksTUFBTSxLQUFLLE1BQU07QUFDakMsVUFBTSxVQUFVLElBQUksb0JBQTRCLENBQUMsU0FBUyxXQUFXO0FBQ2pFLFdBQUssUUFBUSxLQUFLLFNBQVMsRUFBRSxLQUFLLFNBQVMsTUFBTTtBQUFBLElBQ3JELEdBQUcsQ0FBQyxVQUEwQixVQUFVLFNBQVMsV0FBVyxLQUFLLENBQUM7QUFDbEUsV0FBTztBQUFBLEVBQ1g7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxPQUFPLE9BQWtCLE9BQW9DO0FBQ3pELFVBQU0sSUFBSSxJQUFJLG9CQUFzQixNQUFNO0FBQUEsSUFBQyxDQUFDO0FBQzVDLE1BQUUsT0FBTyxLQUFLO0FBQ2QsV0FBTztBQUFBLEVBQ1g7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBWUEsT0FBTyxRQUFtQixjQUFzQixPQUFvQztBQUNoRixVQUFNLFVBQVUsSUFBSSxvQkFBc0IsTUFBTTtBQUFBLElBQUMsQ0FBQztBQUNsRCxRQUFJLGVBQWUsT0FBTyxnQkFBZ0IsY0FBYyxZQUFZLFdBQVcsT0FBTyxZQUFZLFlBQVksWUFBWTtBQUN0SCxrQkFBWSxRQUFRLFlBQVksRUFBRSxpQkFBaUIsU0FBUyxNQUFNLEtBQUssUUFBUSxPQUFPLEtBQUssQ0FBQztBQUFBLElBQ2hHLE9BQU87QUFDSCxpQkFBVyxNQUFNLEtBQUssUUFBUSxPQUFPLEtBQUssR0FBRyxZQUFZO0FBQUEsSUFDN0Q7QUFDQSxXQUFPO0FBQUEsRUFDWDtBQUFBLEVBaUJBLE9BQU8sTUFBZ0IsY0FBc0IsT0FBa0M7QUFDM0UsV0FBTyxJQUFJLG9CQUFzQixDQUFDLFlBQVk7QUFDMUMsaUJBQVcsTUFBTSxRQUFRLEtBQU0sR0FBRyxZQUFZO0FBQUEsSUFDbEQsQ0FBQztBQUFBLEVBQ0w7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFPQSxPQUFPLE9BQWtCLFFBQXFDO0FBQzFELFdBQU8sSUFBSSxvQkFBc0IsQ0FBQyxHQUFHLFdBQVcsT0FBTyxNQUFNLENBQUM7QUFBQSxFQUNsRTtBQUFBLEVBb0JBLE9BQU8sUUFBa0IsT0FBNEQ7QUFDakYsUUFBSSxpQkFBaUIscUJBQW9CO0FBRXJDLGFBQU87QUFBQSxJQUNYO0FBQ0EsV0FBTyxJQUFJLG9CQUF3QixDQUFDLFlBQVksUUFBUSxLQUFLLENBQUM7QUFBQSxFQUNsRTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVVBLE9BQU8sZ0JBQXVEO0FBQzFELFFBQUksU0FBNkMsRUFBRSxhQUFhLEtBQUs7QUFDckUsV0FBTyxVQUFVLElBQUksb0JBQXNCLENBQUMsU0FBUyxXQUFXO0FBQzVELGFBQU8sVUFBVTtBQUNqQixhQUFPLFNBQVM7QUFBQSxJQUNwQixHQUFHLENBQUMsVUFBZ0I7QUF6ckI1QixVQUFBQTtBQXlyQjhCLE9BQUFBLE1BQUEsT0FBTyxnQkFBUCxnQkFBQUEsSUFBQSxhQUFxQjtBQUFBLElBQVEsQ0FBQztBQUNwRCxXQUFPO0FBQUEsRUFDWDtBQUNKO0FBTUEsU0FBUyxhQUFnQixTQUE2QyxPQUFnQztBQUNsRyxNQUFJLHNCQUFnRDtBQUVwRCxTQUFPLENBQUMsV0FBa0Q7QUFDdEQsUUFBSSxDQUFDLE1BQU0sU0FBUztBQUNoQixZQUFNLFVBQVU7QUFDaEIsWUFBTSxTQUFTO0FBQ2YsY0FBUSxPQUFPLE1BQU07QUFNckIsV0FBSyxRQUFRLFVBQVUsS0FBSyxLQUFLLFFBQVEsU0FBUyxRQUFXLENBQUMsUUFBUTtBQUNsRSxZQUFJLFFBQVEsUUFBUTtBQUNoQixnQkFBTTtBQUFBLFFBQ1Y7QUFBQSxNQUNKLENBQUM7QUFBQSxJQUNMO0FBSUEsUUFBSSxDQUFDLE1BQU0sVUFBVSxDQUFDLFFBQVEsYUFBYTtBQUFFO0FBQUEsSUFBUTtBQUVyRCwwQkFBc0IsSUFBSSxRQUFjLENBQUMsWUFBWTtBQUNqRCxVQUFJO0FBQ0EsZ0JBQVEsUUFBUSxZQUFhLE1BQU0sT0FBUSxLQUFLLENBQUM7QUFBQSxNQUNyRCxTQUFTLEtBQUs7QUFDVixnQkFBUSxPQUFPLElBQUksd0JBQXdCLFFBQVEsU0FBUyxLQUFLLDhDQUE4QyxDQUFDO0FBQUEsTUFDcEg7QUFBQSxJQUNKLENBQUMsRUFBRSxNQUFNLENBQUNDLFlBQVk7QUFDbEIsY0FBUSxPQUFPLElBQUksd0JBQXdCLFFBQVEsU0FBU0EsU0FBUSw4Q0FBOEMsQ0FBQztBQUFBLElBQ3ZILENBQUM7QUFHRCxZQUFRLGNBQWM7QUFFdEIsV0FBTztBQUFBLEVBQ1g7QUFDSjtBQUtBLFNBQVMsWUFBZSxTQUE2QyxPQUErRDtBQUNoSSxTQUFPLENBQUMsVUFBVTtBQUNkLFFBQUksTUFBTSxXQUFXO0FBQUU7QUFBQSxJQUFRO0FBQy9CLFVBQU0sWUFBWTtBQUVsQixRQUFJLFVBQVUsUUFBUSxTQUFTO0FBQzNCLFVBQUksTUFBTSxTQUFTO0FBQUU7QUFBQSxNQUFRO0FBQzdCLFlBQU0sVUFBVTtBQUNoQixjQUFRLE9BQU8sSUFBSSxVQUFVLDJDQUEyQyxDQUFDO0FBQ3pFO0FBQUEsSUFDSjtBQUVBLFFBQUksU0FBUyxTQUFTLE9BQU8sVUFBVSxZQUFZLE9BQU8sVUFBVSxhQUFhO0FBQzdFLFVBQUk7QUFDSixVQUFJO0FBQ0EsZUFBUSxNQUFjO0FBQUEsTUFDMUIsU0FBUyxLQUFLO0FBQ1YsY0FBTSxVQUFVO0FBQ2hCLGdCQUFRLE9BQU8sR0FBRztBQUNsQjtBQUFBLE1BQ0o7QUFFQSxVQUFJLGlCQUFXLElBQUksR0FBRztBQUNsQixZQUFJO0FBQ0EsY0FBSSxTQUFVLE1BQWM7QUFDNUIsY0FBSSxpQkFBVyxNQUFNLEdBQUc7QUFDcEIsa0JBQU0sY0FBYyxDQUFDLFVBQWdCO0FBQ2pDLHNCQUFRLE1BQU0sUUFBUSxPQUFPLENBQUMsS0FBSyxDQUFDO0FBQUEsWUFDeEM7QUFDQSxnQkFBSSxNQUFNLFFBQVE7QUFJZCxtQkFBSyxhQUFhLGlDQUFLLFVBQUwsRUFBYyxZQUFZLElBQUcsS0FBSyxFQUFFLE1BQU0sTUFBTTtBQUFBLFlBQ3RFLE9BQU87QUFDSCxzQkFBUSxjQUFjO0FBQUEsWUFDMUI7QUFBQSxVQUNKO0FBQUEsUUFDSixTQUFRO0FBQUEsUUFBQztBQUVULGNBQU0sV0FBb0M7QUFBQSxVQUN0QyxNQUFNLE1BQU07QUFBQSxVQUNaLFdBQVc7QUFBQSxVQUNYLElBQUksVUFBVTtBQUFFLG1CQUFPLEtBQUssS0FBSztBQUFBLFVBQVE7QUFBQSxVQUN6QyxJQUFJLFFBQVFDLFFBQU87QUFBRSxpQkFBSyxLQUFLLFVBQVVBO0FBQUEsVUFBTztBQUFBLFVBQ2hELElBQUksU0FBUztBQUFFLG1CQUFPLEtBQUssS0FBSztBQUFBLFVBQU87QUFBQSxRQUMzQztBQUVBLGNBQU0sV0FBVyxZQUFZLFNBQVMsUUFBUTtBQUM5QyxZQUFJO0FBQ0Esa0JBQVEsTUFBTSxNQUFNLE9BQU8sQ0FBQyxZQUFZLFNBQVMsUUFBUSxHQUFHLFFBQVEsQ0FBQztBQUFBLFFBQ3pFLFNBQVMsS0FBSztBQUNWLG1CQUFTLEdBQUc7QUFBQSxRQUNoQjtBQUNBO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFFQSxRQUFJLE1BQU0sU0FBUztBQUFFO0FBQUEsSUFBUTtBQUM3QixVQUFNLFVBQVU7QUFDaEIsWUFBUSxRQUFRLEtBQUs7QUFBQSxFQUN6QjtBQUNKO0FBS0EsU0FBUyxZQUFlLFNBQTZDLE9BQTREO0FBQzdILFNBQU8sQ0FBQyxXQUFZO0FBQ2hCLFFBQUksTUFBTSxXQUFXO0FBQUU7QUFBQSxJQUFRO0FBQy9CLFVBQU0sWUFBWTtBQUVsQixRQUFJLE1BQU0sU0FBUztBQUNmLFVBQUk7QUFDQSxZQUFJLGtCQUFrQixlQUFlLE1BQU0sa0JBQWtCLGVBQWUsT0FBTyxHQUFHLE9BQU8sT0FBTyxNQUFNLE9BQU8sS0FBSyxHQUFHO0FBRXJIO0FBQUEsUUFDSjtBQUFBLE1BQ0osU0FBUTtBQUFBLE1BQUM7QUFFVCxXQUFLLFFBQVEsT0FBTyxJQUFJLHdCQUF3QixRQUFRLFNBQVMsTUFBTSxDQUFDO0FBQUEsSUFDNUUsT0FBTztBQUNILFlBQU0sVUFBVTtBQUNoQixjQUFRLE9BQU8sTUFBTTtBQUFBLElBQ3pCO0FBQUEsRUFDSjtBQUNKO0FBTUEsU0FBUyxVQUFVLFFBQXFDLFFBQWUsT0FBNEI7QUFDL0YsUUFBTSxVQUFVLENBQUM7QUFFakIsYUFBVyxTQUFTLFFBQVE7QUFDeEIsUUFBSTtBQUNKLFFBQUk7QUFDQSxVQUFJLENBQUMsaUJBQVcsTUFBTSxJQUFJLEdBQUc7QUFBRTtBQUFBLE1BQVU7QUFDekMsZUFBUyxNQUFNO0FBQ2YsVUFBSSxDQUFDLGlCQUFXLE1BQU0sR0FBRztBQUFFO0FBQUEsTUFBVTtBQUFBLElBQ3pDLFNBQVE7QUFBRTtBQUFBLElBQVU7QUFFcEIsUUFBSTtBQUNKLFFBQUk7QUFDQSxlQUFTLFFBQVEsTUFBTSxRQUFRLE9BQU8sQ0FBQyxLQUFLLENBQUM7QUFBQSxJQUNqRCxTQUFTLEtBQUs7QUFDVixjQUFRLE9BQU8sSUFBSSx3QkFBd0IsUUFBUSxLQUFLLHVDQUF1QyxDQUFDO0FBQ2hHO0FBQUEsSUFDSjtBQUVBLFFBQUksQ0FBQyxRQUFRO0FBQUU7QUFBQSxJQUFVO0FBQ3pCLFlBQVE7QUFBQSxPQUNILGtCQUFrQixVQUFXLFNBQVMsUUFBUSxRQUFRLE1BQU0sR0FBRyxNQUFNLENBQUMsV0FBWTtBQUMvRSxnQkFBUSxPQUFPLElBQUksd0JBQXdCLFFBQVEsUUFBUSx1Q0FBdUMsQ0FBQztBQUFBLE1BQ3ZHLENBQUM7QUFBQSxJQUNMO0FBQUEsRUFDSjtBQUVBLFNBQU8sUUFBUSxJQUFJLE9BQU87QUFDOUI7QUFLQSxTQUFTLFNBQVksR0FBUztBQUMxQixTQUFPO0FBQ1g7QUFLQSxTQUFTLFFBQVEsUUFBcUI7QUFDbEMsUUFBTTtBQUNWO0FBS0EsU0FBUyxhQUFhLEtBQWtCO0FBQ3BDLE1BQUk7QUFDQSxRQUFJLGVBQWUsU0FBUyxPQUFPLFFBQVEsWUFBWSxJQUFJLGFBQWEsT0FBTyxVQUFVLFVBQVU7QUFDL0YsYUFBTyxLQUFLO0FBQUEsSUFDaEI7QUFBQSxFQUNKLFNBQVE7QUFBQSxFQUFDO0FBRVQsTUFBSTtBQUNBLFdBQU8sS0FBSyxVQUFVLEdBQUc7QUFBQSxFQUM3QixTQUFRO0FBQUEsRUFBQztBQUVULE1BQUk7QUFDQSxXQUFPLE9BQU8sVUFBVSxTQUFTLEtBQUssR0FBRztBQUFBLEVBQzdDLFNBQVE7QUFBQSxFQUFDO0FBRVQsU0FBTztBQUNYO0FBS0EsU0FBUyxlQUFrQixTQUErQztBQTk0QjFFLE1BQUFGO0FBKzRCSSxNQUFJLE9BQTJDQSxNQUFBLFFBQVEsVUFBVSxNQUFsQixPQUFBQSxNQUF1QixDQUFDO0FBQ3ZFLE1BQUksRUFBRSxhQUFhLE1BQU07QUFDckIsV0FBTyxPQUFPLEtBQUsscUJBQTJCLENBQUM7QUFBQSxFQUNuRDtBQUNBLE1BQUksUUFBUSxVQUFVLEtBQUssTUFBTTtBQUM3QixRQUFJLFFBQVM7QUFDYixZQUFRLFVBQVUsSUFBSTtBQUFBLEVBQzFCO0FBQ0EsU0FBTyxJQUFJO0FBQ2Y7QUFHQSxJQUFJLHVCQUF1QixRQUFRO0FBQ25DLElBQUksd0JBQXdCLE9BQU8seUJBQXlCLFlBQVk7QUFDcEUseUJBQXVCLHFCQUFxQixLQUFLLE9BQU87QUFDNUQsT0FBTztBQUNILHlCQUF1QixXQUF3QztBQUMzRCxRQUFJO0FBQ0osUUFBSTtBQUNKLFVBQU0sVUFBVSxJQUFJLFFBQVcsQ0FBQyxLQUFLLFFBQVE7QUFBRSxnQkFBVTtBQUFLLGVBQVM7QUFBQSxJQUFLLENBQUM7QUFDN0UsV0FBTyxFQUFFLFNBQVMsU0FBUyxPQUFPO0FBQUEsRUFDdEM7QUFDSjs7O0FGdDVCQSxPQUFPLFNBQVMsT0FBTyxVQUFVLENBQUM7QUFDbEMsT0FBTyxPQUFPLG9CQUFvQjtBQUNsQyxPQUFPLE9BQU8sbUJBQW1CO0FBSWpDLElBQU1HLFFBQU8saUJBQWlCLFlBQVksSUFBSTtBQUM5QyxJQUFNLGFBQWEsaUJBQWlCLFlBQVksVUFBVTtBQUMxRCxJQUFNLGdCQUFnQixvQkFBSSxJQUE4QjtBQUV4RCxJQUFNLGNBQWM7QUFDcEIsSUFBTSxlQUFlO0FBMEJkLElBQU0sZUFBTixjQUEyQixNQUFNO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBTXBDLFlBQVksU0FBa0IsU0FBd0I7QUFDbEQsVUFBTSxTQUFTLE9BQU87QUFDdEIsU0FBSyxPQUFPO0FBQUEsRUFDaEI7QUFDSjtBQVNBLFNBQVMsY0FBYyxJQUFZLE1BQWMsUUFBdUI7QUFDcEUsUUFBTSxZQUFZQyxzQkFBcUIsRUFBRTtBQUN6QyxNQUFJLENBQUMsV0FBVztBQUNaO0FBQUEsRUFDSjtBQUVBLE1BQUksQ0FBQyxNQUFNO0FBQ1AsY0FBVSxRQUFRLE1BQVM7QUFBQSxFQUMvQixXQUFXLENBQUMsUUFBUTtBQUNoQixjQUFVLFFBQVEsSUFBSTtBQUFBLEVBQzFCLE9BQU87QUFDSCxRQUFJO0FBQ0EsZ0JBQVUsUUFBUSxLQUFLLE1BQU0sSUFBSSxDQUFDO0FBQUEsSUFDdEMsU0FBUyxLQUFVO0FBQ2YsZ0JBQVUsT0FBTyxJQUFJLFVBQVUsNkJBQTZCLElBQUksU0FBUyxFQUFFLE9BQU8sSUFBSSxDQUFDLENBQUM7QUFBQSxJQUM1RjtBQUFBLEVBQ0o7QUFDSjtBQVNBLFNBQVMsYUFBYSxJQUFZLE1BQWMsUUFBdUI7QUFDbkUsUUFBTSxZQUFZQSxzQkFBcUIsRUFBRTtBQUN6QyxNQUFJLENBQUMsV0FBVztBQUNaO0FBQUEsRUFDSjtBQUVBLE1BQUksQ0FBQyxRQUFRO0FBQ1QsY0FBVSxPQUFPLElBQUksTUFBTSxJQUFJLENBQUM7QUFBQSxFQUNwQyxPQUFPO0FBQ0gsUUFBSTtBQUNKLFFBQUk7QUFDQSxjQUFRLEtBQUssTUFBTSxJQUFJO0FBQUEsSUFDM0IsU0FBUyxLQUFVO0FBQ2YsZ0JBQVUsT0FBTyxJQUFJLFVBQVUsNEJBQTRCLElBQUksU0FBUyxFQUFFLE9BQU8sSUFBSSxDQUFDLENBQUM7QUFDdkY7QUFBQSxJQUNKO0FBRUEsUUFBSSxVQUF3QixDQUFDO0FBQzdCLFFBQUksTUFBTSxPQUFPO0FBQ2IsY0FBUSxRQUFRLE1BQU07QUFBQSxJQUMxQjtBQUVBLFFBQUk7QUFDSixZQUFRLE1BQU0sTUFBTTtBQUFBLE1BQ2hCLEtBQUs7QUFDRCxvQkFBWSxJQUFJLGVBQWUsTUFBTSxTQUFTLE9BQU87QUFDckQ7QUFBQSxNQUNKLEtBQUs7QUFDRCxvQkFBWSxJQUFJLFVBQVUsTUFBTSxTQUFTLE9BQU87QUFDaEQ7QUFBQSxNQUNKLEtBQUs7QUFDRCxvQkFBWSxJQUFJLGFBQWEsTUFBTSxTQUFTLE9BQU87QUFDbkQ7QUFBQSxNQUNKO0FBQ0ksb0JBQVksSUFBSSxNQUFNLE1BQU0sU0FBUyxPQUFPO0FBQzVDO0FBQUEsSUFDUjtBQUVBLGNBQVUsT0FBTyxTQUFTO0FBQUEsRUFDOUI7QUFDSjtBQVFBLFNBQVNBLHNCQUFxQixJQUEwQztBQUNwRSxRQUFNLFdBQVcsY0FBYyxJQUFJLEVBQUU7QUFDckMsZ0JBQWMsT0FBTyxFQUFFO0FBQ3ZCLFNBQU87QUFDWDtBQU9BLFNBQVNDLGNBQXFCO0FBQzFCLE1BQUk7QUFDSixLQUFHO0FBQ0MsYUFBUyxPQUFPO0FBQUEsRUFDcEIsU0FBUyxjQUFjLElBQUksTUFBTTtBQUNqQyxTQUFPO0FBQ1g7QUFjTyxTQUFTLEtBQUssU0FBK0M7QUFDaEUsUUFBTSxLQUFLQSxZQUFXO0FBRXRCLFFBQU0sU0FBUyxtQkFBbUIsY0FBbUI7QUFDckQsZ0JBQWMsSUFBSSxJQUFJLEVBQUUsU0FBUyxPQUFPLFNBQVMsUUFBUSxPQUFPLE9BQU8sQ0FBQztBQUV4RSxRQUFNLFVBQVVGLE1BQUssYUFBYSxPQUFPLE9BQU8sRUFBRSxXQUFXLEdBQUcsR0FBRyxPQUFPLENBQUM7QUFDM0UsTUFBSSxVQUFVO0FBRWQsVUFBUSxLQUFLLE1BQU07QUFDZixjQUFVO0FBQUEsRUFDZCxHQUFHLENBQUMsUUFBUTtBQUNSLGtCQUFjLE9BQU8sRUFBRTtBQUN2QixXQUFPLE9BQU8sR0FBRztBQUFBLEVBQ3JCLENBQUM7QUFFRCxRQUFNLFNBQVMsTUFBTTtBQUNqQixrQkFBYyxPQUFPLEVBQUU7QUFDdkIsV0FBTyxXQUFXLGNBQWMsRUFBQyxXQUFXLEdBQUUsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxRQUFRO0FBQzVELGNBQVEsTUFBTSxxREFBcUQsR0FBRztBQUFBLElBQzFFLENBQUM7QUFBQSxFQUNMO0FBRUEsU0FBTyxjQUFjLE1BQU07QUFDdkIsUUFBSSxTQUFTO0FBQ1QsYUFBTyxPQUFPO0FBQUEsSUFDbEIsT0FBTztBQUNILGFBQU8sUUFBUSxLQUFLLE1BQU07QUFBQSxJQUM5QjtBQUFBLEVBQ0o7QUFFQSxTQUFPLE9BQU87QUFDbEI7QUFVTyxTQUFTLE9BQU8sZUFBdUIsTUFBc0M7QUFDaEYsU0FBTyxLQUFLLEVBQUUsWUFBWSxLQUFLLENBQUM7QUFDcEM7QUFVTyxTQUFTLEtBQUssYUFBcUIsTUFBc0M7QUFDNUUsU0FBTyxLQUFLLEVBQUUsVUFBVSxLQUFLLENBQUM7QUFDbEM7OztBR3hPQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBWUEsSUFBTUcsUUFBTyxpQkFBaUIsWUFBWSxTQUFTO0FBRW5ELElBQU0sbUJBQW1CO0FBQ3pCLElBQU0sZ0JBQWdCO0FBUWYsU0FBUyxRQUFRLE1BQTZCO0FBQ2pELFNBQU9BLE1BQUssa0JBQWtCLEVBQUMsS0FBSSxDQUFDO0FBQ3hDO0FBT08sU0FBUyxPQUF3QjtBQUNwQyxTQUFPQSxNQUFLLGFBQWE7QUFDN0I7OztBQ2xDQTtBQUFBO0FBQUE7QUFBQSxlQUFBQztBQUFBLEVBQUE7QUFBQSxhQUFBQztBQUFBLEVBQUE7QUFBQTtBQUFBO0FBYU8sU0FBUyxJQUFhLFFBQWdCO0FBQ3pDLFNBQU87QUFDWDtBQU1PLFNBQVMsVUFBVSxRQUFxQjtBQUMzQyxTQUFTLFVBQVUsT0FBUSxLQUFLO0FBQ3BDO0FBT08sU0FBU0MsT0FBZSxTQUFtRDtBQUM5RSxNQUFJLFlBQVksS0FBSztBQUNqQixXQUFPLENBQUMsV0FBWSxXQUFXLE9BQU8sQ0FBQyxJQUFJO0FBQUEsRUFDL0M7QUFFQSxTQUFPLENBQUMsV0FBVztBQUNmLFFBQUksV0FBVyxNQUFNO0FBQ2pCLGFBQU8sQ0FBQztBQUFBLElBQ1o7QUFDQSxhQUFTLElBQUksR0FBRyxJQUFJLE9BQU8sUUFBUSxLQUFLO0FBQ3BDLGFBQU8sQ0FBQyxJQUFJLFFBQVEsT0FBTyxDQUFDLENBQUM7QUFBQSxJQUNqQztBQUNBLFdBQU87QUFBQSxFQUNYO0FBQ0o7QUFPTyxTQUFTQyxLQUFhLEtBQThCLE9BQStEO0FBQ3RILE1BQUksVUFBVSxLQUFLO0FBQ2YsV0FBTyxDQUFDLFdBQVksV0FBVyxPQUFPLENBQUMsSUFBSTtBQUFBLEVBQy9DO0FBRUEsU0FBTyxDQUFDLFdBQVc7QUFDZixRQUFJLFdBQVcsTUFBTTtBQUNqQixhQUFPLENBQUM7QUFBQSxJQUNaO0FBQ0EsZUFBV0MsUUFBTyxRQUFRO0FBQ3RCLGFBQU9BLElBQUcsSUFBSSxNQUFNLE9BQU9BLElBQUcsQ0FBQztBQUFBLElBQ25DO0FBQ0EsV0FBTztBQUFBLEVBQ1g7QUFDSjtBQU1PLFNBQVMsU0FBa0IsU0FBMEQ7QUFDeEYsTUFBSSxZQUFZLEtBQUs7QUFDakIsV0FBTztBQUFBLEVBQ1g7QUFFQSxTQUFPLENBQUMsV0FBWSxXQUFXLE9BQU8sT0FBTyxRQUFRLE1BQU07QUFDL0Q7QUFNTyxTQUFTLE9BQU8sYUFFdkI7QUFDSSxNQUFJLFNBQVM7QUFDYixhQUFXLFFBQVEsYUFBYTtBQUM1QixRQUFJLFlBQVksSUFBSSxNQUFNLEtBQUs7QUFDM0IsZUFBUztBQUNUO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFDQSxNQUFJLFFBQVE7QUFDUixXQUFPO0FBQUEsRUFDWDtBQUVBLFNBQU8sQ0FBQyxXQUFXO0FBQ2YsZUFBVyxRQUFRLGFBQWE7QUFDNUIsVUFBSSxRQUFRLFFBQVE7QUFDaEIsZUFBTyxJQUFJLElBQUksWUFBWSxJQUFJLEVBQUUsT0FBTyxJQUFJLENBQUM7QUFBQSxNQUNqRDtBQUFBLElBQ0o7QUFDQSxXQUFPO0FBQUEsRUFDWDtBQUNKOzs7QUN6R0E7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBd0RBLElBQU1DLFFBQU8saUJBQWlCLFlBQVksT0FBTztBQUVqRCxJQUFNLFNBQVM7QUFDZixJQUFNLGFBQWE7QUFDbkIsSUFBTSxhQUFhO0FBT1osU0FBUyxTQUE0QjtBQUN4QyxTQUFPQSxNQUFLLE1BQU07QUFDdEI7QUFPTyxTQUFTLGFBQThCO0FBQzFDLFNBQU9BLE1BQUssVUFBVTtBQUMxQjtBQU9PLFNBQVMsYUFBOEI7QUFDMUMsU0FBT0EsTUFBSyxVQUFVO0FBQzFCOzs7QXRCNUVBLE9BQU8sU0FBUyxPQUFPLFVBQVUsQ0FBQztBQTRDbEMsT0FBTyxPQUFPLFNBQWdCO0FBQ3ZCLE9BQU8scUJBQXFCOyIsCiAgIm5hbWVzIjogWyJfYSIsICJFcnJvciIsICJjYWxsIiwgIl9hIiwgIkVycm9yIiwgImNhbGwiLCAiX2EiLCAicmVzaXphYmxlIiwgImNhbGwiLCAiX2EiLCAiY2FsbCIsICJjYWxsIiwgIkhpZGVNZXRob2QiLCAiU2hvd01ldGhvZCIsICJpc0RvY3VtZW50RG90QWxsIiwgIl9hIiwgInJlYXNvbiIsICJ2YWx1ZSIsICJjYWxsIiwgImdldEFuZERlbGV0ZVJlc3BvbnNlIiwgImdlbmVyYXRlSUQiLCAiY2FsbCIsICJBcnJheSIsICJNYXAiLCAiQXJyYXkiLCAiTWFwIiwgImtleSIsICJjYWxsIl0KfQo= diff --git a/v3/internal/assetserver/bundledassets/runtime.js b/v3/internal/assetserver/bundledassets/runtime.js new file mode 100644 index 000000000..3646984a5 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime.js @@ -0,0 +1 @@ +var Fe=Object.defineProperty,an=Object.defineProperties;var ln=Object.getOwnPropertyDescriptors;var ke=Object.getOwnPropertySymbols;var cn=Object.prototype.hasOwnProperty,dn=Object.prototype.propertyIsEnumerable;var Oe=(n,e,i)=>e in n?Fe(n,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):n[e]=i,Le=(n,e)=>{for(var i in e||(e={}))cn.call(e,i)&&Oe(n,i,e[i]);if(ke)for(var i of ke(e))dn.call(e,i)&&Oe(n,i,e[i]);return n},Ue=(n,e)=>an(n,ln(e));var u=(n,e)=>{for(var i in e)Fe(n,i,{get:e[i],enumerable:!0})};var ce={};u(ce,{Application:()=>ve,Browser:()=>_,Call:()=>Te,CancelError:()=>k,CancellablePromise:()=>j,CancelledRejectionError:()=>W,Clipboard:()=>xe,Create:()=>Ae,Dialogs:()=>ee,Events:()=>oe,Flags:()=>we,Screens:()=>Ee,System:()=>ue,WML:()=>le,Window:()=>Z});var le={};u(le,{Enable:()=>ae,Reload:()=>Ke});var _={};u(_,{OpenURL:()=>q});var mn="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";function P(n=21){let e="",i=n|0;for(;i--;)e+=mn[Math.random()*64|0];return e}var un=window.location.origin+"/wails/runtime",d=Object.freeze({Call:0,Clipboard:1,Application:2,Events:3,ContextMenu:4,Dialog:5,Window:6,Screens:7,System:8,Browser:9,CancelCall:10}),wn=P();function m(n,e=""){return function(i,o=null){return pn(n,i,e,o)}}async function pn(n,e,i,o){var l,c;let t=new URL(un);t.searchParams.append("object",n.toString()),t.searchParams.append("method",e.toString()),o&&t.searchParams.append("args",JSON.stringify(o));let r={"x-wails-client-id":wn};i&&(r["x-wails-window-name"]=i);let a=await fetch(t,{headers:r});if(!a.ok)throw new Error(await a.text());return((c=(l=a.headers.get("Content-Type"))==null?void 0:l.indexOf("application/json"))!=null?c:-1)!==-1?a.json():a.text()}var fn=m(d.Browser),gn=0;function q(n){return fn(gn,{url:n.toString()})}var ee={};u(ee,{Error:()=>An,Info:()=>Tn,OpenFile:()=>Rn,Question:()=>$,SaveFile:()=>En,Warning:()=>xn});window._wails=window._wails||{};window._wails.dialogErrorCallback=Mn;window._wails.dialogResultCallback=Dn;var hn=m(d.Dialog),F=new Map,Wn=0,bn=1,vn=2,yn=3,Cn=4,Pn=5;function Dn(n,e,i){let o=Ie(n);if(o)if(i)try{o.resolve(JSON.parse(e))}catch(t){o.reject(new TypeError("could not parse result: "+t.message,{cause:t}))}else o.resolve(e)}function Mn(n,e){var i;(i=Ie(n))==null||i.reject(new window.Error(e))}function Ie(n){let e=F.get(n);return F.delete(n),e}function Sn(){let n;do n=P();while(F.has(n));return n}function D(n,e={}){let i=Sn();return new Promise((o,t)=>{F.set(i,{resolve:o,reject:t}),hn(n,Object.assign({"dialog-id":i},e)).catch(r=>{F.delete(i),t(r)})})}function Tn(n){return D(Wn,n)}function xn(n){return D(bn,n)}function An(n){return D(vn,n)}function $(n){return D(yn,n)}function Rn(n){var e;return(e=D(Cn,n))!=null?e:[]}function En(n){return D(Pn,n)}var oe={};u(oe,{Emit:()=>ie,Off:()=>In,OffAll:()=>zn,On:()=>Ln,OnMultiple:()=>ne,Once:()=>Un,Types:()=>je,WailsEvent:()=>M});var p=new Map,B=class{constructor(e,i,o){this.eventName=e,this.callback=i,this.maxCallbacks=o||-1}dispatch(e){try{this.callback(e)}catch(i){}return this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0)}};function ze(n){let e=p.get(n.eventName);e&&(e=e.filter(i=>i!==n),e.length===0?p.delete(n.eventName):p.set(n.eventName,e))}var je=Object.freeze({Windows:Object.freeze({APMPowerSettingChange:"windows:APMPowerSettingChange",APMPowerStatusChange:"windows:APMPowerStatusChange",APMResumeAutomatic:"windows:APMResumeAutomatic",APMResumeSuspend:"windows:APMResumeSuspend",APMSuspend:"windows:APMSuspend",ApplicationStarted:"windows:ApplicationStarted",SystemThemeChanged:"windows:SystemThemeChanged",WebViewNavigationCompleted:"windows:WebViewNavigationCompleted",WindowActive:"windows:WindowActive",WindowBackgroundErase:"windows:WindowBackgroundErase",WindowClickActive:"windows:WindowClickActive",WindowClosing:"windows:WindowClosing",WindowDidMove:"windows:WindowDidMove",WindowDidResize:"windows:WindowDidResize",WindowDPIChanged:"windows:WindowDPIChanged",WindowDragDrop:"windows:WindowDragDrop",WindowDragEnter:"windows:WindowDragEnter",WindowDragLeave:"windows:WindowDragLeave",WindowDragOver:"windows:WindowDragOver",WindowEndMove:"windows:WindowEndMove",WindowEndResize:"windows:WindowEndResize",WindowFullscreen:"windows:WindowFullscreen",WindowHide:"windows:WindowHide",WindowInactive:"windows:WindowInactive",WindowKeyDown:"windows:WindowKeyDown",WindowKeyUp:"windows:WindowKeyUp",WindowKillFocus:"windows:WindowKillFocus",WindowNonClientHit:"windows:WindowNonClientHit",WindowNonClientMouseDown:"windows:WindowNonClientMouseDown",WindowNonClientMouseLeave:"windows:WindowNonClientMouseLeave",WindowNonClientMouseMove:"windows:WindowNonClientMouseMove",WindowNonClientMouseUp:"windows:WindowNonClientMouseUp",WindowPaint:"windows:WindowPaint",WindowRestore:"windows:WindowRestore",WindowSetFocus:"windows:WindowSetFocus",WindowShow:"windows:WindowShow",WindowStartMove:"windows:WindowStartMove",WindowStartResize:"windows:WindowStartResize",WindowUnFullscreen:"windows:WindowUnFullscreen",WindowZOrderChanged:"windows:WindowZOrderChanged",WindowMinimise:"windows:WindowMinimise",WindowUnMinimise:"windows:WindowUnMinimise",WindowMaximise:"windows:WindowMaximise",WindowUnMaximise:"windows:WindowUnMaximise"}),Mac:Object.freeze({ApplicationDidBecomeActive:"mac:ApplicationDidBecomeActive",ApplicationDidChangeBackingProperties:"mac:ApplicationDidChangeBackingProperties",ApplicationDidChangeEffectiveAppearance:"mac:ApplicationDidChangeEffectiveAppearance",ApplicationDidChangeIcon:"mac:ApplicationDidChangeIcon",ApplicationDidChangeOcclusionState:"mac:ApplicationDidChangeOcclusionState",ApplicationDidChangeScreenParameters:"mac:ApplicationDidChangeScreenParameters",ApplicationDidChangeStatusBarFrame:"mac:ApplicationDidChangeStatusBarFrame",ApplicationDidChangeStatusBarOrientation:"mac:ApplicationDidChangeStatusBarOrientation",ApplicationDidChangeTheme:"mac:ApplicationDidChangeTheme",ApplicationDidFinishLaunching:"mac:ApplicationDidFinishLaunching",ApplicationDidHide:"mac:ApplicationDidHide",ApplicationDidResignActive:"mac:ApplicationDidResignActive",ApplicationDidUnhide:"mac:ApplicationDidUnhide",ApplicationDidUpdate:"mac:ApplicationDidUpdate",ApplicationShouldHandleReopen:"mac:ApplicationShouldHandleReopen",ApplicationWillBecomeActive:"mac:ApplicationWillBecomeActive",ApplicationWillFinishLaunching:"mac:ApplicationWillFinishLaunching",ApplicationWillHide:"mac:ApplicationWillHide",ApplicationWillResignActive:"mac:ApplicationWillResignActive",ApplicationWillTerminate:"mac:ApplicationWillTerminate",ApplicationWillUnhide:"mac:ApplicationWillUnhide",ApplicationWillUpdate:"mac:ApplicationWillUpdate",MenuDidAddItem:"mac:MenuDidAddItem",MenuDidBeginTracking:"mac:MenuDidBeginTracking",MenuDidClose:"mac:MenuDidClose",MenuDidDisplayItem:"mac:MenuDidDisplayItem",MenuDidEndTracking:"mac:MenuDidEndTracking",MenuDidHighlightItem:"mac:MenuDidHighlightItem",MenuDidOpen:"mac:MenuDidOpen",MenuDidPopUp:"mac:MenuDidPopUp",MenuDidRemoveItem:"mac:MenuDidRemoveItem",MenuDidSendAction:"mac:MenuDidSendAction",MenuDidSendActionToItem:"mac:MenuDidSendActionToItem",MenuDidUpdate:"mac:MenuDidUpdate",MenuWillAddItem:"mac:MenuWillAddItem",MenuWillBeginTracking:"mac:MenuWillBeginTracking",MenuWillDisplayItem:"mac:MenuWillDisplayItem",MenuWillEndTracking:"mac:MenuWillEndTracking",MenuWillHighlightItem:"mac:MenuWillHighlightItem",MenuWillOpen:"mac:MenuWillOpen",MenuWillPopUp:"mac:MenuWillPopUp",MenuWillRemoveItem:"mac:MenuWillRemoveItem",MenuWillSendAction:"mac:MenuWillSendAction",MenuWillSendActionToItem:"mac:MenuWillSendActionToItem",MenuWillUpdate:"mac:MenuWillUpdate",WebViewDidCommitNavigation:"mac:WebViewDidCommitNavigation",WebViewDidFinishNavigation:"mac:WebViewDidFinishNavigation",WebViewDidReceiveServerRedirectForProvisionalNavigation:"mac:WebViewDidReceiveServerRedirectForProvisionalNavigation",WebViewDidStartProvisionalNavigation:"mac:WebViewDidStartProvisionalNavigation",WindowDidBecomeKey:"mac:WindowDidBecomeKey",WindowDidBecomeMain:"mac:WindowDidBecomeMain",WindowDidBeginSheet:"mac:WindowDidBeginSheet",WindowDidChangeAlpha:"mac:WindowDidChangeAlpha",WindowDidChangeBackingLocation:"mac:WindowDidChangeBackingLocation",WindowDidChangeBackingProperties:"mac:WindowDidChangeBackingProperties",WindowDidChangeCollectionBehavior:"mac:WindowDidChangeCollectionBehavior",WindowDidChangeEffectiveAppearance:"mac:WindowDidChangeEffectiveAppearance",WindowDidChangeOcclusionState:"mac:WindowDidChangeOcclusionState",WindowDidChangeOrderingMode:"mac:WindowDidChangeOrderingMode",WindowDidChangeScreen:"mac:WindowDidChangeScreen",WindowDidChangeScreenParameters:"mac:WindowDidChangeScreenParameters",WindowDidChangeScreenProfile:"mac:WindowDidChangeScreenProfile",WindowDidChangeScreenSpace:"mac:WindowDidChangeScreenSpace",WindowDidChangeScreenSpaceProperties:"mac:WindowDidChangeScreenSpaceProperties",WindowDidChangeSharingType:"mac:WindowDidChangeSharingType",WindowDidChangeSpace:"mac:WindowDidChangeSpace",WindowDidChangeSpaceOrderingMode:"mac:WindowDidChangeSpaceOrderingMode",WindowDidChangeTitle:"mac:WindowDidChangeTitle",WindowDidChangeToolbar:"mac:WindowDidChangeToolbar",WindowDidDeminiaturize:"mac:WindowDidDeminiaturize",WindowDidEndSheet:"mac:WindowDidEndSheet",WindowDidEnterFullScreen:"mac:WindowDidEnterFullScreen",WindowDidEnterVersionBrowser:"mac:WindowDidEnterVersionBrowser",WindowDidExitFullScreen:"mac:WindowDidExitFullScreen",WindowDidExitVersionBrowser:"mac:WindowDidExitVersionBrowser",WindowDidExpose:"mac:WindowDidExpose",WindowDidFocus:"mac:WindowDidFocus",WindowDidMiniaturize:"mac:WindowDidMiniaturize",WindowDidMove:"mac:WindowDidMove",WindowDidOrderOffScreen:"mac:WindowDidOrderOffScreen",WindowDidOrderOnScreen:"mac:WindowDidOrderOnScreen",WindowDidResignKey:"mac:WindowDidResignKey",WindowDidResignMain:"mac:WindowDidResignMain",WindowDidResize:"mac:WindowDidResize",WindowDidUpdate:"mac:WindowDidUpdate",WindowDidUpdateAlpha:"mac:WindowDidUpdateAlpha",WindowDidUpdateCollectionBehavior:"mac:WindowDidUpdateCollectionBehavior",WindowDidUpdateCollectionProperties:"mac:WindowDidUpdateCollectionProperties",WindowDidUpdateShadow:"mac:WindowDidUpdateShadow",WindowDidUpdateTitle:"mac:WindowDidUpdateTitle",WindowDidUpdateToolbar:"mac:WindowDidUpdateToolbar",WindowDidZoom:"mac:WindowDidZoom",WindowFileDraggingEntered:"mac:WindowFileDraggingEntered",WindowFileDraggingExited:"mac:WindowFileDraggingExited",WindowFileDraggingPerformed:"mac:WindowFileDraggingPerformed",WindowHide:"mac:WindowHide",WindowMaximise:"mac:WindowMaximise",WindowUnMaximise:"mac:WindowUnMaximise",WindowMinimise:"mac:WindowMinimise",WindowUnMinimise:"mac:WindowUnMinimise",WindowShouldClose:"mac:WindowShouldClose",WindowShow:"mac:WindowShow",WindowWillBecomeKey:"mac:WindowWillBecomeKey",WindowWillBecomeMain:"mac:WindowWillBecomeMain",WindowWillBeginSheet:"mac:WindowWillBeginSheet",WindowWillChangeOrderingMode:"mac:WindowWillChangeOrderingMode",WindowWillClose:"mac:WindowWillClose",WindowWillDeminiaturize:"mac:WindowWillDeminiaturize",WindowWillEnterFullScreen:"mac:WindowWillEnterFullScreen",WindowWillEnterVersionBrowser:"mac:WindowWillEnterVersionBrowser",WindowWillExitFullScreen:"mac:WindowWillExitFullScreen",WindowWillExitVersionBrowser:"mac:WindowWillExitVersionBrowser",WindowWillFocus:"mac:WindowWillFocus",WindowWillMiniaturize:"mac:WindowWillMiniaturize",WindowWillMove:"mac:WindowWillMove",WindowWillOrderOffScreen:"mac:WindowWillOrderOffScreen",WindowWillOrderOnScreen:"mac:WindowWillOrderOnScreen",WindowWillResignMain:"mac:WindowWillResignMain",WindowWillResize:"mac:WindowWillResize",WindowWillUnfocus:"mac:WindowWillUnfocus",WindowWillUpdate:"mac:WindowWillUpdate",WindowWillUpdateAlpha:"mac:WindowWillUpdateAlpha",WindowWillUpdateCollectionBehavior:"mac:WindowWillUpdateCollectionBehavior",WindowWillUpdateCollectionProperties:"mac:WindowWillUpdateCollectionProperties",WindowWillUpdateShadow:"mac:WindowWillUpdateShadow",WindowWillUpdateTitle:"mac:WindowWillUpdateTitle",WindowWillUpdateToolbar:"mac:WindowWillUpdateToolbar",WindowWillUpdateVisibility:"mac:WindowWillUpdateVisibility",WindowWillUseStandardFrame:"mac:WindowWillUseStandardFrame",WindowZoomIn:"mac:WindowZoomIn",WindowZoomOut:"mac:WindowZoomOut",WindowZoomReset:"mac:WindowZoomReset"}),Linux:Object.freeze({ApplicationStartup:"linux:ApplicationStartup",SystemThemeChanged:"linux:SystemThemeChanged",WindowDeleteEvent:"linux:WindowDeleteEvent",WindowDidMove:"linux:WindowDidMove",WindowDidResize:"linux:WindowDidResize",WindowFocusIn:"linux:WindowFocusIn",WindowFocusOut:"linux:WindowFocusOut",WindowLoadChanged:"linux:WindowLoadChanged"}),Common:Object.freeze({ApplicationOpenedWithFile:"common:ApplicationOpenedWithFile",ApplicationStarted:"common:ApplicationStarted",ThemeChanged:"common:ThemeChanged",WindowClosing:"common:WindowClosing",WindowDidMove:"common:WindowDidMove",WindowDidResize:"common:WindowDidResize",WindowDPIChanged:"common:WindowDPIChanged",WindowFilesDropped:"common:WindowFilesDropped",WindowFocus:"common:WindowFocus",WindowFullscreen:"common:WindowFullscreen",WindowHide:"common:WindowHide",WindowLostFocus:"common:WindowLostFocus",WindowMaximise:"common:WindowMaximise",WindowMinimise:"common:WindowMinimise",WindowToggleFrameless:"common:WindowToggleFrameless",WindowRestore:"common:WindowRestore",WindowRuntimeReady:"common:WindowRuntimeReady",WindowShow:"common:WindowShow",WindowUnFullscreen:"common:WindowUnFullscreen",WindowUnMaximise:"common:WindowUnMaximise",WindowUnMinimise:"common:WindowUnMinimise",WindowZoom:"common:WindowZoom",WindowZoomIn:"common:WindowZoomIn",WindowZoomOut:"common:WindowZoomOut",WindowZoomReset:"common:WindowZoomReset"})});window._wails=window._wails||{};window._wails.dispatchWailsEvent=Fn;var kn=m(d.Events),On=0,M=class{constructor(e,i=null){this.name=e,this.data=i}};function Fn(n){let e=p.get(n.name);if(!e)return;let i=new M(n.name,n.data);"sender"in n&&(i.sender=n.sender),e=e.filter(o=>!o.dispatch(i)),e.length===0?p.delete(n.name):p.set(n.name,e)}function ne(n,e,i){let o=p.get(n)||[],t=new B(n,e,i);return o.push(t),p.set(n,o),()=>ze(t)}function Ln(n,e){return ne(n,e,-1)}function Un(n,e){return ne(n,e,1)}function In(...n){n.forEach(e=>p.delete(e))}function zn(){p.clear()}function ie(n,e){let i;return typeof n=="object"&&n!==null&&"name"in n&&"data"in n?i=new M(n.name,n.data):i=new M(n,e),kn(On,i)}function Be(){return new MouseEvent("mousedown").buttons===0}function He(){if(!EventTarget||!AbortSignal||!AbortController)return!1;let n=!0,e=new EventTarget,i=new AbortController;return e.addEventListener("test",()=>{n=!1},{signal:i.signal}),i.abort(),e.dispatchEvent(new CustomEvent("test")),n}function H(n){var e;return n.target instanceof HTMLElement?n.target:!(n.target instanceof HTMLElement)&&n.target instanceof Node&&(e=n.target.parentElement)!=null?e:document.body}var Ne=!1;document.addEventListener("DOMContentLoaded",()=>{Ne=!0});function Ze(n){Ne||document.readyState==="complete"?n():document.addEventListener("DOMContentLoaded",n)}var jn=0,Bn=1,Hn=2,Nn=3,Zn=4,Vn=5,Gn=6,Kn=7,Yn=8,Xn=9,Jn=10,Qn=11,qn=12,_n=13,$n=14,ei=15,ni=16,ii=17,oi=18,ti=19,ri=20,si=21,ai=22,li=23,ci=24,di=25,mi=26,ui=27,wi=28,pi=29,fi=30,gi=31,hi=32,Wi=33,bi=34,vi=35,yi=36,Ci=37,Pi=38,Di=39,Mi=40,Si=41,Ti=42,xi=43,Ai=44,Ri=45,Ei=46,ki=47,Oi=48,s=Symbol("caller");s;var N=class N{constructor(e=""){this[s]=m(d.Window,e);for(let i of Object.getOwnPropertyNames(N.prototype))i!=="constructor"&&typeof this[i]=="function"&&(this[i]=this[i].bind(this))}Get(e){return new N(e)}Position(){return this[s](jn)}Center(){return this[s](Bn)}Close(){return this[s](Hn)}DisableSizeConstraints(){return this[s](Nn)}EnableSizeConstraints(){return this[s](Zn)}Focus(){return this[s](Vn)}ForceReload(){return this[s](Gn)}Fullscreen(){return this[s](Kn)}GetScreen(){return this[s](Yn)}GetZoom(){return this[s](Xn)}Height(){return this[s](Jn)}Hide(){return this[s](Qn)}IsFocused(){return this[s](qn)}IsFullscreen(){return this[s](_n)}IsMaximised(){return this[s]($n)}IsMinimised(){return this[s](ei)}Maximise(){return this[s](ni)}Minimise(){return this[s](ii)}Name(){return this[s](oi)}OpenDevTools(){return this[s](ti)}RelativePosition(){return this[s](ri)}Reload(){return this[s](si)}Resizable(){return this[s](ai)}Restore(){return this[s](li)}SetPosition(e,i){return this[s](ci,{x:e,y:i})}SetAlwaysOnTop(e){return this[s](di,{alwaysOnTop:e})}SetBackgroundColour(e,i,o,t){return this[s](mi,{r:e,g:i,b:o,a:t})}SetFrameless(e){return this[s](ui,{frameless:e})}SetFullscreenButtonEnabled(e){return this[s](wi,{enabled:e})}SetMaxSize(e,i){return this[s](pi,{width:e,height:i})}SetMinSize(e,i){return this[s](fi,{width:e,height:i})}SetRelativePosition(e,i){return this[s](gi,{x:e,y:i})}SetResizable(e){return this[s](hi,{resizable:e})}SetSize(e,i){return this[s](Wi,{width:e,height:i})}SetTitle(e){return this[s](bi,{title:e})}SetZoom(e){return this[s](vi,{zoom:e})}Show(){return this[s](yi)}Size(){return this[s](Ci)}ToggleFullscreen(){return this[s](Pi)}ToggleMaximise(){return this[s](Di)}ToggleFrameless(){return this[s](Mi)}UnFullscreen(){return this[s](Si)}UnMaximise(){return this[s](Ti)}UnMinimise(){return this[s](xi)}Width(){return this[s](Ai)}Zoom(){return this[s](Ri)}ZoomIn(){return this[s](Ei)}ZoomOut(){return this[s](ki)}ZoomReset(){return this[s](Oi)}},te=N,Fi=new te(""),Z=Fi;function Li(n,e=null){ie(n,e)}function Ui(n,e){let i=Z.Get(n),o=i[e];if(typeof o=="function")try{o.call(i)}catch(t){}}function Ve(n){let e=n.currentTarget;function i(t="Yes"){if(t!=="Yes")return;let r=e.getAttribute("wml-event")||e.getAttribute("data-wml-event"),a=e.getAttribute("wml-target-window")||e.getAttribute("data-wml-target-window")||"",l=e.getAttribute("wml-window")||e.getAttribute("data-wml-window"),c=e.getAttribute("wml-openurl")||e.getAttribute("data-wml-openurl");r!==null&&Li(r),l!==null&&Ui(a,l),c!==null&&q(c)}let o=e.getAttribute("wml-confirm")||e.getAttribute("data-wml-confirm");o?$({Title:"Confirm",Message:o,Detached:!1,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(i):i()}var L=Symbol("controller"),S=Symbol("triggerMap"),b=Symbol("elementCount");L;var re=class{constructor(){this[L]=new AbortController}set(e,i){return{signal:this[L].signal}}reset(){this[L].abort(),this[L]=new AbortController}};S,b;var se=class{constructor(){this[S]=new WeakMap,this[b]=0}set(e,i){return this[S].has(e)||this[b]++,this[S].set(e,i),{}}reset(){if(!(this[b]<=0)){for(let e of document.body.querySelectorAll("*")){if(this[b]<=0)break;let i=this[S].get(e);i!=null&&this[b]--;for(let o of i||[])e.removeEventListener(o,Ve)}this[S]=new WeakMap,this[b]=0}}},Ge=He()?new re:new se;function Ii(n){let e=/\S+/g,i=n.getAttribute("wml-trigger")||n.getAttribute("data-wml-trigger")||"click",o=[],t;for(;(t=e.exec(i))!==null;)o.push(t[0]);let r=Ge.set(n,o);for(let a of o)n.addEventListener(a,Ve,r)}function ae(){Ze(Ke)}function Ke(){Ge.reset(),document.body.querySelectorAll("[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]").forEach(Ii)}window.wails=ce;ae();var ue={};u(ue,{Capabilities:()=>Hi,Environment:()=>Ni,IsAMD64:()=>Gi,IsARM:()=>Ki,IsARM64:()=>Yi,IsDarkMode:()=>Bi,IsDebug:()=>me,IsLinux:()=>Zi,IsMac:()=>Vi,IsWindows:()=>V,invoke:()=>v});var Ye=m(d.System),zi=0,ji=1,de=function(){var n,e,i,o,t;try{if((e=(n=window.chrome)==null?void 0:n.webview)!=null&&e.postMessage)return window.chrome.webview.postMessage.bind(window.chrome.webview);if((t=(o=(i=window.webkit)==null?void 0:i.messageHandlers)==null?void 0:o.external)!=null&&t.postMessage)return window.webkit.messageHandlers.external.postMessage.bind(window.webkit.messageHandlers.external)}catch(r){}return null}();function v(n){de==null||de(n)}function Bi(){return Ye(zi)}async function Hi(){let n=await fetch("/wails/capabilities");if(n.ok)return n.json();throw new Error("could not fetch capabilities: "+n.statusText)}function Ni(){return Ye(ji)}function V(){return window._wails.environment.OS==="windows"}function Zi(){return window._wails.environment.OS==="linux"}function Vi(){return window._wails.environment.OS==="darwin"}function Gi(){return window._wails.environment.Arch==="amd64"}function Ki(){return window._wails.environment.Arch==="arm"}function Yi(){return window._wails.environment.Arch==="arm64"}function me(){return!!window._wails.environment.Debug}window.addEventListener("contextmenu",qi);var Xi=m(d.ContextMenu),Ji=0;function Qi(n,e,i,o){Xi(Ji,{id:n,x:e,y:i,data:o})}function qi(n){let e=H(n),i=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu").trim();if(i){n.preventDefault();let o=window.getComputedStyle(e).getPropertyValue("--custom-contextmenu-data");Qi(i,n.clientX,n.clientY,o)}else _i(n,e)}function _i(n,e){if(me())return;switch(window.getComputedStyle(e).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":n.preventDefault();return}if(e.isContentEditable)return;let i=window.getSelection(),o=i&&i.toString().length>0;if(o)for(let t=0;tU});function U(n){try{return window._wails.flags[n]}catch(e){throw new Error("Unable to retrieve flag '"+n+"': "+e,{cause:e})}}var I=!1,z=!1,he=!1,x=!1,A=!1,y="",Xe="auto",T=0,pe=Be();window._wails=window._wails||{};window._wails.setResizable=n=>{he=n,he||(x=A=!1,w())};window.addEventListener("mousedown",We,{capture:!0});window.addEventListener("mousemove",We,{capture:!0});window.addEventListener("mouseup",We,{capture:!0});for(let n of["click","contextmenu","dblclick"])window.addEventListener(n,$i,{capture:!0});function $i(n){(z||A)&&(n.stopImmediatePropagation(),n.stopPropagation(),n.preventDefault())}var fe=0,eo=1,ge=2;function We(n){let e,i=n.buttons;switch(n.type){case"mousedown":e=fe,pe||(i=T|1<lo,Quit:()=>mo,Show:()=>co});var be=m(d.Application),ro=0,so=1,ao=2;function lo(){return be(ro)}function co(){return be(so)}function mo(){return be(ao)}var Te={};u(Te,{ByID:()=>Oo,ByName:()=>ko,Call:()=>Se,RuntimeError:()=>X});var Qe=Function.prototype.toString,R=typeof Reflect=="object"&&Reflect!==null&&Reflect.apply,ye,G;if(typeof R=="function"&&typeof Object.defineProperty=="function")try{ye=Object.defineProperty({},"length",{get:function(){throw G}}),G={},R(function(){throw 42},null,ye)}catch(n){n!==G&&(R=null)}else R=null;var uo=/^\s*class\b/,Pe=function(e){try{var i=Qe.call(e);return uo.test(i)}catch(o){return!1}},Ce=function(e){try{return Pe(e)?!1:(Qe.call(e),!0)}catch(i){return!1}},K=Object.prototype.toString,wo="[object Object]",po="[object Function]",fo="[object GeneratorFunction]",go="[object HTMLAllCollection]",ho="[object HTML document.all class]",Wo="[object HTMLCollection]",bo=typeof Symbol=="function"&&!!Symbol.toStringTag,vo=!(0 in[,]),De=function(){return!1};typeof document=="object"&&(Je=document.all,K.call(Je)===K.call(document.all)&&(De=function(e){if((vo||!e)&&(typeof e>"u"||typeof e=="object"))try{var i=K.call(e);return(i===go||i===ho||i===Wo||i===wo)&&e("")==null}catch(o){}return!1}));var Je;function yo(n){if(De(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;try{R(n,null,ye)}catch(e){if(e!==G)return!1}return!Pe(n)&&Ce(n)}function Co(n){if(De(n))return!0;if(!n||typeof n!="function"&&typeof n!="object")return!1;if(bo)return Ce(n);if(Pe(n))return!1;var e=K.call(n);return e!==po&&e!==fo&&!/^\[object HTML/.test(e)?!1:Ce(n)}var h=R?yo:Co;var k=class extends Error{constructor(e,i){super(e,i),this.name="CancelError"}},W=class extends Error{constructor(e,i,o){super((o!=null?o:"Unhandled rejection in cancelled promise.")+" Reason: "+Po(i),{cause:i}),this.promise=e,this.name="CancelledRejectionError"}},f=Symbol("barrier"),Me=Symbol("cancelImpl"),en,qe=(en=Symbol.species)!=null?en:Symbol("speciesPolyfill"),j=class n extends Promise{constructor(e,i){let o,t;if(super((c,g)=>{o=c,t=g}),this.constructor[qe]!==Promise)throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");let r={promise:this,resolve:o,reject:t,get oncancelled(){return i!=null?i:null},set oncancelled(c){i=c!=null?c:void 0}},a={get root(){return a},resolving:!1,settled:!1};Object.defineProperties(this,{[f]:{configurable:!1,enumerable:!1,writable:!0,value:null},[Me]:{configurable:!1,enumerable:!1,writable:!1,value:nn(r,a)}});let l=tn(r,a);try{e(on(r,a),l)}catch(c){a.resolving||l(c)}}cancel(e){return new n(i=>{Promise.all([this[Me](new k("Promise cancelled.",{cause:e})),Do(this)]).then(()=>i(),()=>i())})}cancelOn(e){return e.aborted?this.cancel(e.reason):e.addEventListener("abort",()=>void this.cancel(e.reason),{capture:!0}),this}then(e,i,o){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");if(h(e)||(e=_e),h(i)||(i=$e),e===_e&&i==$e)return new n(r=>r(this));let t={};return this[f]=t,new n((r,a)=>{super.then(l=>{var c;this[f]===t&&(this[f]=null),(c=t.resolve)==null||c.call(t);try{r(e(l))}catch(g){a(g)}},l=>{var c;this[f]===t&&(this[f]=null),(c=t.resolve)==null||c.call(t);try{r(i(l))}catch(g){a(g)}})},async r=>{try{return o==null?void 0:o(r)}finally{await this.cancel(r)}})}catch(e,i){return this.then(void 0,e,i)}finally(e,i){if(!(this instanceof n))throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");return h(e)?this.then(o=>n.resolve(e()).then(()=>o),o=>n.resolve(e()).then(()=>{throw o}),i):this.then(e,e,i)}static get[(f,Me,qe)](){return Promise}static all(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.all(i).then(t,r)},t=>Y(o,i,t));return o}static allSettled(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.allSettled(i).then(t,r)},t=>Y(o,i,t));return o}static any(e){let i=Array.from(e),o=i.length===0?n.resolve(i):new n((t,r)=>{Promise.any(i).then(t,r)},t=>Y(o,i,t));return o}static race(e){let i=Array.from(e),o=new n((t,r)=>{Promise.race(i).then(t,r)},t=>Y(o,i,t));return o}static cancel(e){let i=new n(()=>{});return i.cancel(e),i}static timeout(e,i){let o=new n(()=>{});return AbortSignal&&typeof AbortSignal=="function"&&AbortSignal.timeout&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(e).addEventListener("abort",()=>void o.cancel(i)):setTimeout(()=>void o.cancel(i),e),o}static sleep(e,i){return new n(o=>{setTimeout(()=>o(i),e)})}static reject(e){return new n((i,o)=>o(e))}static resolve(e){return e instanceof n?e:new n(i=>i(e))}static withResolvers(){let e={oncancelled:null};return e.promise=new n((i,o)=>{e.resolve=i,e.reject=o},i=>{var o;(o=e.oncancelled)==null||o.call(e,i)}),e}};function nn(n,e){let i;return o=>{if(e.settled||(e.settled=!0,e.reason=o,n.reject(o),Promise.prototype.then.call(n.promise,void 0,t=>{if(t!==o)throw t})),!(!e.reason||!n.oncancelled))return i=new Promise(t=>{try{t(n.oncancelled(e.reason.cause))}catch(r){Promise.reject(new W(n.promise,r,"Unhandled exception in oncancelled callback."))}}).catch(t=>{Promise.reject(new W(n.promise,t,"Unhandled rejection in oncancelled callback."))}),n.oncancelled=null,i}}function on(n,e){return i=>{if(!e.resolving){if(e.resolving=!0,i===n.promise){if(e.settled)return;e.settled=!0,n.reject(new TypeError("A promise cannot be resolved with itself."));return}if(i!=null&&(typeof i=="object"||typeof i=="function")){let o;try{o=i.then}catch(t){e.settled=!0,n.reject(t);return}if(h(o)){try{let a=i.cancel;if(h(a)){let l=c=>{Reflect.apply(a,i,[c])};e.reason?nn(Ue(Le({},n),{oncancelled:l}),e)(e.reason):n.oncancelled=l}}catch(a){}let t={root:e.root,resolving:!1,get settled(){return this.root.settled},set settled(a){this.root.settled=a},get reason(){return this.root.reason}},r=tn(n,t);try{Reflect.apply(o,i,[on(n,t),r])}catch(a){r(a)}return}}e.settled||(e.settled=!0,n.resolve(i))}}}function tn(n,e){return i=>{if(!e.resolving)if(e.resolving=!0,e.settled){try{if(i instanceof k&&e.reason instanceof k&&Object.is(i.cause,e.reason.cause))return}catch(o){}Promise.reject(new W(n.promise,i))}else e.settled=!0,n.reject(i)}}function Y(n,e,i){let o=[];for(let t of e){let r;try{if(!h(t.then)||(r=t.cancel,!h(r)))continue}catch(l){continue}let a;try{a=Reflect.apply(r,t,[i])}catch(l){Promise.reject(new W(n,l,"Unhandled exception in cancel method."));continue}a&&o.push((a instanceof Promise?a:Promise.resolve(a)).catch(l=>{Promise.reject(new W(n,l,"Unhandled rejection in cancel method."))}))}return Promise.all(o)}function _e(n){return n}function $e(n){throw n}function Po(n){try{if(n instanceof Error||typeof n!="object"||n.toString!==Object.prototype.toString)return""+n}catch(e){}try{return JSON.stringify(n)}catch(e){}try{return Object.prototype.toString.call(n)}catch(e){}return""}function Do(n){var i;let e=(i=n[f])!=null?i:{};return"promise"in e||Object.assign(e,E()),n[f]==null&&(e.resolve(),n[f]=e),e.promise}var E=Promise.withResolvers;E&&typeof E=="function"?E=E.bind(Promise):E=function(){let n,e;return{promise:new Promise((o,t)=>{n=o,e=t}),resolve:n,reject:e}};window._wails=window._wails||{};window._wails.callResultHandler=Ao;window._wails.callErrorHandler=Ro;var Mo=m(d.Call),So=m(d.CancelCall),O=new Map,To=0,xo=0,X=class extends Error{constructor(e,i){super(e,i),this.name="RuntimeError"}};function Ao(n,e,i){let o=rn(n);if(o)if(!e)o.resolve(void 0);else if(!i)o.resolve(e);else try{o.resolve(JSON.parse(e))}catch(t){o.reject(new TypeError("could not parse result: "+t.message,{cause:t}))}}function Ro(n,e,i){let o=rn(n);if(o)if(!i)o.reject(new Error(e));else{let t;try{t=JSON.parse(e)}catch(l){o.reject(new TypeError("could not parse error: "+l.message,{cause:l}));return}let r={};t.cause&&(r.cause=t.cause);let a;switch(t.kind){case"ReferenceError":a=new ReferenceError(t.message,r);break;case"TypeError":a=new TypeError(t.message,r);break;case"RuntimeError":a=new X(t.message,r);break;default:a=new Error(t.message,r);break}o.reject(a)}}function rn(n){let e=O.get(n);return O.delete(n),e}function Eo(){let n;do n=P();while(O.has(n));return n}function Se(n){let e=Eo(),i=j.withResolvers();O.set(e,{resolve:i.resolve,reject:i.reject});let o=Mo(To,Object.assign({"call-id":e},n)),t=!1;o.then(()=>{t=!0},a=>{O.delete(e),i.reject(a)});let r=()=>(O.delete(e),So(xo,{"call-id":e}).catch(a=>{}));return i.oncancelled=()=>t?r():o.then(r),i.promise}function ko(n,...e){return Se({methodName:n,args:e})}function Oo(n,...e){return Se({methodID:n,args:e})}var xe={};u(xe,{SetText:()=>Uo,Text:()=>Io});var sn=m(d.Clipboard),Fo=0,Lo=1;function Uo(n){return sn(Fo,{text:n})}function Io(){return sn(Lo)}var Ae={};u(Ae,{Any:()=>C,Array:()=>jo,ByteSlice:()=>zo,Map:()=>Bo,Nullable:()=>Ho,Struct:()=>No});function C(n){return n}function zo(n){return n==null?"":n}function jo(n){return n===C?e=>e===null?[]:e:e=>{if(e===null)return[];for(let i=0;ii===null?{}:i:i=>{if(i===null)return{};for(let o in i)i[o]=e(i[o]);return i}}function Ho(n){return n===C?C:e=>e===null?null:n(e)}function No(n){let e=!0;for(let i in n)if(n[i]!==C){e=!1;break}return e?C:i=>{for(let o in n)o in i&&(i[o]=n[o](i[o]));return i}}var Ee={};u(Ee,{GetAll:()=>Ko,GetCurrent:()=>Xo,GetPrimary:()=>Yo});var Re=m(d.Screens),Zo=0,Vo=1,Go=2;function Ko(){return Re(Zo)}function Yo(){return Re(Vo)}function Xo(){return Re(Go)}window._wails=window._wails||{};window._wails.invoke=v;v("wails:runtime:ready");export{ve as Application,_ as Browser,Te as Call,k as CancelError,j as CancellablePromise,W as CancelledRejectionError,xe as Clipboard,Ae as Create,ee as Dialogs,oe as Events,we as Flags,Ee as Screens,ue as System,le as WML,Z as Window}; diff --git a/v3/internal/assetserver/bundledassets/runtime_dev.go b/v3/internal/assetserver/bundledassets/runtime_dev.go new file mode 100644 index 000000000..2c9621a66 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime_dev.go @@ -0,0 +1,8 @@ +//go:build !production + +package bundledassets + +import _ "embed" + +//go:embed runtime.debug.js +var RuntimeJS []byte diff --git a/v3/internal/assetserver/bundledassets/runtime_production.go b/v3/internal/assetserver/bundledassets/runtime_production.go new file mode 100644 index 000000000..9614a7b13 --- /dev/null +++ b/v3/internal/assetserver/bundledassets/runtime_production.go @@ -0,0 +1,8 @@ +//go:build production + +package bundledassets + +import _ "embed" + +//go:embed runtime.js +var RuntimeJS []byte diff --git a/v3/internal/assetserver/common.go b/v3/internal/assetserver/common.go new file mode 100644 index 000000000..ce6201b6a --- /dev/null +++ b/v3/internal/assetserver/common.go @@ -0,0 +1,61 @@ +package assetserver + +import ( + "bytes" + "context" + "fmt" + "io" + "log/slog" + "net/http" + "strings" +) + +const ( + HeaderHost = "Host" + HeaderContentType = "Content-Type" + HeaderContentLength = "Content-Length" + HeaderUserAgent = "User-Agent" + // TODO: Is this needed? + HeaderCacheControl = "Cache-Control" + HeaderUpgrade = "Upgrade" + + WailsUserAgentValue = "wails.io" +) + +type assetServerLogger struct{} + +var assetServerLoggerKey assetServerLogger + +func ServeFile(rw http.ResponseWriter, filename string, blob []byte) error { + header := rw.Header() + header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) + if mimeType := header.Get(HeaderContentType); mimeType == "" { + mimeType = GetMimetype(filename, blob) + header.Set(HeaderContentType, mimeType) + } + + rw.WriteHeader(http.StatusOK) + _, err := io.Copy(rw, bytes.NewReader(blob)) + return err +} + +func isWebSocket(req *http.Request) bool { + upgrade := req.Header.Get(HeaderUpgrade) + return strings.EqualFold(upgrade, "websocket") +} + +func contextWithLogger(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, assetServerLoggerKey, logger) +} + +func logInfo(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil { + logger.Info(message, args...) + } +} + +func logError(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil { + logger.Error(message, args...) + } +} diff --git a/v3/internal/assetserver/content_type_sniffer.go b/v3/internal/assetserver/content_type_sniffer.go new file mode 100644 index 000000000..e36db90e8 --- /dev/null +++ b/v3/internal/assetserver/content_type_sniffer.go @@ -0,0 +1,134 @@ +package assetserver + +import ( + "net/http" +) + +func newContentTypeSniffer(rw http.ResponseWriter) *contentTypeSniffer { + return &contentTypeSniffer{ + rw: rw, + closeChannel: make(chan bool, 1), + } +} + +type contentTypeSniffer struct { + rw http.ResponseWriter + prefix []byte + status int + headerCommitted bool + headerWritten bool + closeChannel chan bool +} + +// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController]. +func (rw *contentTypeSniffer) Unwrap() http.ResponseWriter { + return rw.rw +} + +func (rw *contentTypeSniffer) Header() http.Header { + return rw.rw.Header() +} + +func (rw *contentTypeSniffer) Write(chunk []byte) (int, error) { + if !rw.headerCommitted { + rw.WriteHeader(http.StatusOK) + } + + if rw.headerWritten { + return rw.rw.Write(chunk) + } + + if len(chunk) == 0 { + return 0, nil + } + + // Cut away at most 512 bytes from chunk, and not less than 0. + cut := max(min(len(chunk), 512-len(rw.prefix)), 0) + if cut >= 512 { + // Avoid copying data if a full prefix is available on first non-zero write. + cut = len(chunk) + rw.prefix = chunk + chunk = nil + } else if cut > 0 { + // First write had less than 512 bytes -- copy data to the prefix buffer. + if rw.prefix == nil { + // Preallocate space for the prefix to be used for sniffing. + rw.prefix = make([]byte, 0, 512) + } + rw.prefix = append(rw.prefix, chunk[:cut]...) + chunk = chunk[cut:] + } + + if len(rw.prefix) < 512 { + return cut, nil + } + + if _, err := rw.complete(); err != nil { + return cut, err + } + + n, err := rw.rw.Write(chunk) + return cut + n, err +} + +func (rw *contentTypeSniffer) WriteHeader(code int) { + if rw.headerCommitted { + return + } + + rw.status = code + rw.headerCommitted = true + + if _, hasType := rw.Header()[HeaderContentType]; hasType { + rw.rw.WriteHeader(rw.status) + rw.headerWritten = true + } +} + +// sniff sniffs the content type from the stored prefix if necessary, +// then writes the header. +func (rw *contentTypeSniffer) sniff() { + if rw.headerWritten || !rw.headerCommitted { + return + } + + m := rw.Header() + if _, hasType := m[HeaderContentType]; !hasType { + m.Set(HeaderContentType, http.DetectContentType(rw.prefix)) + } + + rw.rw.WriteHeader(rw.status) + rw.headerWritten = true +} + +// complete sniffs the content type if necessary, writes the header +// and sends the data prefix that has been stored for sniffing. +// +// Whoever creates a contentTypeSniffer instance +// is responsible for calling complete after the nested handler has returned. +func (rw *contentTypeSniffer) complete() (n int, err error) { + rw.sniff() + + if rw.headerWritten && len(rw.prefix) > 0 { + n, err = rw.rw.Write(rw.prefix) + rw.prefix = nil + } + + return +} + +// CloseNotify implements the http.CloseNotifier interface. +func (rw *contentTypeSniffer) CloseNotify() <-chan bool { + return rw.closeChannel +} + +func (rw *contentTypeSniffer) closeClient() { + rw.closeChannel <- true +} + +// Flush implements the http.Flusher interface. +func (rw *contentTypeSniffer) Flush() { + if f, ok := rw.rw.(http.Flusher); ok { + f.Flush() + } +} diff --git a/v3/internal/assetserver/defaults/index.en.html b/v3/internal/assetserver/defaults/index.en.html new file mode 100644 index 000000000..72c0f567d --- /dev/null +++ b/v3/internal/assetserver/defaults/index.en.html @@ -0,0 +1,350 @@ + + + + + + Page Not Found - Wails + + + +
+ +
+ +
+ + +
+
+
+
โš ๏ธ
+ Missing index.html file +
+
+

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

+
    +
  • + 1 + + If you are using the Assets option in your application, ensure you have an index.html file in your project's embedded assets directory. +
    + View Example โ†’ +
    +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // ... + app := application.New(application.Options{ + // ... + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + // ... +} +
    +
    +
    +
  • +
  • + 2 + If the file doesn't exist but should, verify that your build process is configured to correctly include the HTML file in the embedded assets directory. +
  • +
  • + 3 + + An alternative solution is to use the HTML option in the WebviewWindow Options. +
    + View Example โ†’ +
    +func main() { + + // ... + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // ... + HTML: "<h1>Hello World!<h1>", + }) + // ... +} +
    +
    +
    +
  • +
+
+ +
+ + +
+
+
+ + + +
+ + \ No newline at end of file diff --git a/v3/internal/assetserver/defaults/index.zh.html b/v3/internal/assetserver/defaults/index.zh.html new file mode 100644 index 000000000..45e5fb93c --- /dev/null +++ b/v3/internal/assetserver/defaults/index.zh.html @@ -0,0 +1,302 @@ + + + + + + ้กต้ขๆœชๆ‰พๅˆฐ - Wails + + + +
+ +
+ +
+ + +
+
+
+
โŒ
+ ๆœชๆ‰พๅˆฐ index.html ๆ–‡ไปถ +
่ฏทๆŒ‰็…งไปฅไธ‹ๆญฅ้ชค่งฃๅ†ณๆญค้—ฎ้ข˜
+
+
+

+ ็ณป็ปŸๆ็คบ๏ผšๅœจๅตŒๅ…ฅ่ต„ๆบไธญๆœช่ƒฝๆ‰พๅˆฐ index.html ๆ–‡ไปถใ€‚ +
+ ไธ็”จๆ‹…ๅฟƒ๏ผŒ่ฟ™ไธช้—ฎ้ข˜ๅพˆๅฎนๆ˜“่งฃๅ†ณใ€‚ +

+
    +
  • + 1 + + ๅฆ‚ๆžœๆ‚จๅœจๅบ”็”จ็จ‹ๅบไธญไฝฟ็”จไบ† Assets ้€‰้กน๏ผŒ่ฏท็กฎไฟๆ‚จ็š„้กน็›ฎๅตŒๅ…ฅ่ต„ๆบ็›ฎๅฝ•ไธญๆœ‰ index.html ๆ–‡ไปถใ€‚ +
    + ๆŸฅ็œ‹็คบไพ‹ โžœ +
    +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // ... + app := application.New(application.Options{ + // ... + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + // ... +} +
    +
    +
    +
  • +
  • + 2 + ๅฆ‚ๆžœๆ–‡ไปถๅบ”่ฏฅๅญ˜ๅœจไฝ†ไธๅญ˜ๅœจ๏ผŒ่ฏท้ชŒ่ฏๆ‚จ็š„ๆž„ๅปบ่ฟ‡็จ‹ๆ˜ฏๅฆ้…็ฝฎๆญฃ็กฎ๏ผŒไปฅ็กฎไฟ HTML ๆ–‡ไปถๅŒ…ๅซๅœจๅตŒๅ…ฅ่ต„ๆบ็›ฎๅฝ•ไธญใ€‚ +
  • +
  • + 3 + + ๅฆไธ€็ง่งฃๅ†ณๆ–นๆกˆๆ˜ฏๅœจ WebviewWindow ้€‰้กนไธญไฝฟ็”จ HTML ้€‰้กนใ€‚ +
    + ๆŸฅ็œ‹็คบไพ‹ โžœ +
    +func main() { + + // ... + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // ... + HTML: "<h1>Hello World!<h1>", + }) + // ... +} +
    +
    +
    +
  • +
+
+ +
+ + +
+
+
+ + + +
+ + diff --git a/v3/internal/assetserver/fallback_response_writer.go b/v3/internal/assetserver/fallback_response_writer.go new file mode 100644 index 000000000..c26d3cc52 --- /dev/null +++ b/v3/internal/assetserver/fallback_response_writer.go @@ -0,0 +1,80 @@ +package assetserver + +import ( + "maps" + "net/http" +) + +// fallbackResponseWriter wraps a [http.ResponseWriter]. +// If the main handler returns status code 404, +// its response is discarded +// and the request is forwarded to the fallback handler. +type fallbackResponseWriter struct { + rw http.ResponseWriter + req *http.Request + fallback http.Handler + + header http.Header + headerWritten bool + complete bool +} + +// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController]. +func (fw *fallbackResponseWriter) Unwrap() http.ResponseWriter { + return fw.rw +} + +func (fw *fallbackResponseWriter) Header() http.Header { + if fw.header == nil { + // Preserve original header in case we get a 404 response. + fw.header = fw.rw.Header().Clone() + } + return fw.header +} + +func (fw *fallbackResponseWriter) Write(chunk []byte) (int, error) { + if fw.complete { + // Fallback triggered, discard further writes. + return len(chunk), nil + } + + if !fw.headerWritten { + fw.WriteHeader(http.StatusOK) + } + + return fw.rw.Write(chunk) +} + +func (fw *fallbackResponseWriter) WriteHeader(statusCode int) { + if fw.headerWritten { + return + } + fw.headerWritten = true + + if statusCode == http.StatusNotFound { + // Protect fallback header from external modifications. + if fw.header == nil { + fw.header = fw.rw.Header().Clone() + } + + // Invoke fallback handler. + fw.complete = true + fw.fallback.ServeHTTP(fw.rw, fw.req) + return + } + + if fw.header != nil { + // Apply headers and forward original map to the main handler. + maps.Copy(fw.rw.Header(), fw.header) + fw.header = fw.rw.Header() + } + + fw.rw.WriteHeader(statusCode) +} + +// Flush implements the http.Flusher interface. +func (rw *fallbackResponseWriter) Flush() { + if f, ok := rw.rw.(http.Flusher); ok { + f.Flush() + } +} diff --git a/v3/internal/assetserver/fs.go b/v3/internal/assetserver/fs.go new file mode 100644 index 000000000..20ccf3fab --- /dev/null +++ b/v3/internal/assetserver/fs.go @@ -0,0 +1,76 @@ +package assetserver + +import ( + "embed" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// findEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. +func findEmbedRootPath(fileSystem embed.FS) (string, error) { + stopErr := errors.New("files or multiple dirs found") + + fPath := "" + err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + fPath = path + if entries, dErr := fs.ReadDir(fileSystem, path); dErr != nil { + return dErr + } else if len(entries) <= 1 { + return nil + } + } + + return stopErr + }) + + if err != nil && !errors.Is(err, stopErr) { + return "", err + } + + return fPath, nil +} + +func findPathToFile(fileSystem fs.FS, file string) (string, error) { + stat, _ := fs.Stat(fileSystem, file) + if stat != nil { + return ".", nil + } + var indexFiles []string + err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, file) { + indexFiles = append(indexFiles, path) + } + return nil + }) + if err != nil { + return "", err + } + + if len(indexFiles) > 1 { + selected := indexFiles[0] + for _, f := range indexFiles { + if len(f) < len(selected) { + selected = f + } + } + path, _ := filepath.Split(selected) + return path, nil + } + if len(indexFiles) > 0 { + path, _ := filepath.Split(indexFiles[0]) + return path, nil + } + return "", fmt.Errorf("%s: %w", file, os.ErrNotExist) +} diff --git a/v3/internal/assetserver/middleware.go b/v3/internal/assetserver/middleware.go new file mode 100644 index 000000000..b3826ab7d --- /dev/null +++ b/v3/internal/assetserver/middleware.go @@ -0,0 +1,20 @@ +package assetserver + +import ( + "net/http" +) + +// Middleware defines a HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v3/internal/assetserver/mimecache.go b/v3/internal/assetserver/mimecache.go new file mode 100644 index 000000000..c43de519d --- /dev/null +++ b/v3/internal/assetserver/mimecache.go @@ -0,0 +1,67 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "sync" + + "github.com/wailsapp/mimetype" +) + +var ( + mimeCache = map[string]string{} + mimeMutex sync.Mutex + + // The list of builtin mime-types by extension as defined by + // the golang standard lib package "mime" + // The standard lib also takes into account mime type definitions from + // /etc files like '/etc/apache2/mime.types' but we want to have the + // same behaviour on all platforms and not depend on some external file. + mimeTypesByExt = map[string]string{ + ".avif": "image/avif", + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json", + ".mjs": "text/javascript; charset=utf-8", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".wasm": "application/wasm", + ".webp": "image/webp", + ".xml": "text/xml; charset=utf-8", + } +) + +func GetMimetype(filename string, data []byte) string { + mimeMutex.Lock() + defer mimeMutex.Unlock() + + result := mimeTypesByExt[filepath.Ext(filename)] + if result != "" { + return result + } + + result = mimeCache[filename] + if result != "" { + return result + } + + detect := mimetype.Detect(data) + if detect == nil { + result = http.DetectContentType(data) + } else { + result = detect.String() + } + + if result == "" { + result = "application/octet-stream" + } + + mimeCache[filename] = result + return result +} diff --git a/v3/internal/assetserver/mimecache_test.go b/v3/internal/assetserver/mimecache_test.go new file mode 100644 index 000000000..48e5943fa --- /dev/null +++ b/v3/internal/assetserver/mimecache_test.go @@ -0,0 +1,46 @@ +package assetserver + +import ( + "testing" +) + +func TestGetMimetype(t *testing.T) { + type args struct { + filename string + data []byte + } + bomUTF8 := []byte{0xef, 0xbb, 0xbf} + var emptyMsg []byte + css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}") + html := []byte("title") + bomHtml := append(bomUTF8, html...) + svg := []byte("") + svgWithComment := append([]byte(""), svg...) + svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...) + svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...) + + tests := []struct { + name string + args args + want string + }{ + {"nil data", args{"nil.svg", nil}, "image/svg+xml"}, + {"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"}, + {"css", args{"test.css", css}, "text/css; charset=utf-8"}, + {"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"}, + {"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"}, + {"svg", args{"test.svg", svg}, "image/svg+xml"}, + {"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"}, + {"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"}, + {"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want { + t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want) + } + }) + } +} diff --git a/v3/internal/assetserver/options.go b/v3/internal/assetserver/options.go new file mode 100644 index 000000000..ae0570880 --- /dev/null +++ b/v3/internal/assetserver/options.go @@ -0,0 +1,38 @@ +package assetserver + +import ( + "errors" + "log/slog" + "net/http" +) + +// Options defines the configuration of the AssetServer. +type Options struct { + // Handler which serves all the content to the WebView. + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // This middleware injects itself before any of Wails internal middlewares. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // Logger is the logger used by the AssetServer. If not defined, no logging will be done. + Logger *slog.Logger +} + +// Validate the options +func (o Options) Validate() error { + if o.Handler == nil && o.Middleware == nil { + return errors.New("AssetServer options invalid: either Handler or Middleware must be set") + } + + return nil +} diff --git a/v3/internal/assetserver/ringqueue.go b/v3/internal/assetserver/ringqueue.go new file mode 100644 index 000000000..b94e7cd5c --- /dev/null +++ b/v3/internal/assetserver/ringqueue.go @@ -0,0 +1,101 @@ +// Code from https://github.com/erikdubbelboer/ringqueue +/* +The MIT License (MIT) + +Copyright (c) 2015 Erik Dubbelboer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package assetserver + +type ringqueue[T any] struct { + nodes []T + head int + tail int + cnt int + + minSize int +} + +func newRingqueue[T any](minSize uint) *ringqueue[T] { + if minSize < 2 { + minSize = 2 + } + return &ringqueue[T]{ + nodes: make([]T, minSize), + minSize: int(minSize), + } +} + +func (q *ringqueue[T]) resize(n int) { + nodes := make([]T, n) + if q.head < q.tail { + copy(nodes, q.nodes[q.head:q.tail]) + } else { + copy(nodes, q.nodes[q.head:]) + copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail]) + } + + q.tail = q.cnt % n + q.head = 0 + q.nodes = nodes +} + +func (q *ringqueue[T]) Add(i T) { + if q.cnt == len(q.nodes) { + // Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy + // In Go this resulted in a higher memory usage. + q.resize(q.cnt * 2) + } + q.nodes[q.tail] = i + q.tail = (q.tail + 1) % len(q.nodes) + q.cnt++ +} + +func (q *ringqueue[T]) Peek() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + return q.nodes[q.head], true +} + +func (q *ringqueue[T]) Remove() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + i := q.nodes[q.head] + q.head = (q.head + 1) % len(q.nodes) + q.cnt-- + + if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n { + q.resize(n) + } + + return i, true +} + +func (q *ringqueue[T]) Cap() int { + return cap(q.nodes) +} + +func (q *ringqueue[T]) Len() int { + return q.cnt +} diff --git a/v3/internal/assetserver/webview/request.go b/v3/internal/assetserver/webview/request.go new file mode 100644 index 000000000..18ff29890 --- /dev/null +++ b/v3/internal/assetserver/webview/request.go @@ -0,0 +1,17 @@ +package webview + +import ( + "io" + "net/http" +) + +type Request interface { + URL() (string, error) + Method() (string, error) + Header() (http.Header, error) + Body() (io.ReadCloser, error) + + Response() ResponseWriter + + Close() error +} diff --git a/v3/internal/assetserver/webview/request_darwin.go b/v3/internal/assetserver/webview/request_darwin.go new file mode 100644 index 000000000..2d8b00168 --- /dev/null +++ b/v3/internal/assetserver/webview/request_darwin.go @@ -0,0 +1,249 @@ +//go:build darwin + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import +#include + +static void URLSchemeTaskRetain(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask retain]; +} + +static void URLSchemeTaskRelease(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + [urlSchemeTask release]; +} + +static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.URL.absoluteString UTF8String]; + } +} + +static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + return [urlSchemeTask.request.HTTPMethod UTF8String]; + } +} + +static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + NSData *headerData = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil]; + if (!headerData) { + return nil; + } + + NSString* headerString = [[[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding] autorelease]; + const char * headerJSON = [headerString UTF8String]; + + return strdup(headerJSON); + } +} + +static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBody) { + return false; + } + + *body = urlSchemeTask.request.HTTPBody.bytes; + *bodyLen = urlSchemeTask.request.HTTPBody.length; + return true; + } +} + +static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return false; + } + + [urlSchemeTask.request.HTTPBodyStream open]; + return true; + } +} + +static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) { + id urlSchemeTask = (id) wkUrlSchemeTask; + @autoreleasepool { + if (!urlSchemeTask.request.HTTPBodyStream) { + return; + } + + [urlSchemeTask.request.HTTPBodyStream close]; + } +} + +static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) { + id urlSchemeTask = (id) wkUrlSchemeTask; + + @autoreleasepool { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (!stream) { + return -2; + } + + NSStreamStatus status = stream.streamStatus; + if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) { + return 0; + } else if (status != NSStreamStatusOpen) { + return -3; + } + + return [stream read:buf maxLength:bufLen]; + } +} +*/ +import "C" + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "unsafe" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `id` +func NewRequest(wkURLSchemeTask unsafe.Pointer) Request { + C.URLSchemeTaskRetain(wkURLSchemeTask) + return newRequestFinalizer(&request{task: wkURLSchemeTask}) +} + +var _ Request = &request{} + +type request struct { + task unsafe.Pointer + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil +} + +func (r *request) Method() (string, error) { + return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + header := http.Header{} + if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil { + if headers := C.GoString(cHeaders); headers != "" { + var h map[string]string + if err := json.Unmarshal([]byte(headers), &h); err != nil { + return nil, fmt.Errorf("unable to unmarshal request headers: %s", err) + } + + for k, v := range h { + header.Add(k, v) + } + } + C.free(unsafe.Pointer(cHeaders)) + } + r.header = header + return header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + var body unsafe.Pointer + var bodyLen C.int + if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) { + if body != nil && bodyLen > 0 { + r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen))) + } else { + r.body = http.NoBody + } + } else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) { + r.body = &requestBodyStreamReader{task: r.task} + } + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{r: r} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.URLSchemeTaskRelease(r.task) + return err +} + +var _ io.ReadCloser = &requestBodyStreamReader{} + +type requestBodyStreamReader struct { + task unsafe.Pointer + closed bool +} + +// Read implements io.Reader +func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) { + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen)) + if res > 0 { + return int(res), nil + } + + switch res { + case 0: + return 0, io.EOF + case -1: + return 0, errors.New("body: stream error") + case -2: + return 0, errors.New("body: no stream defined") + case -3: + return 0, io.ErrClosedPipe + default: + return 0, fmt.Errorf("body: unknown error %d", res) + } +} + +func (r *requestBodyStreamReader) Close() error { + if r.closed { + return nil + } + r.closed = true + + C.URLSchemeTaskRequestBodyStreamClose(r.task) + return nil +} diff --git a/v3/internal/assetserver/webview/request_finalizer.go b/v3/internal/assetserver/webview/request_finalizer.go new file mode 100644 index 000000000..6a8c6a928 --- /dev/null +++ b/v3/internal/assetserver/webview/request_finalizer.go @@ -0,0 +1,40 @@ +package webview + +import ( + "runtime" + "sync/atomic" +) + +var _ Request = &requestFinalizer{} + +type requestFinalizer struct { + Request + closed int32 +} + +// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer +// if it has not been already closed. +// It also makes sure Close() of the wrapping request is only called once. +func newRequestFinalizer(r Request) Request { + rf := &requestFinalizer{Request: r} + // Make sure to async release since it might block the finalizer goroutine for a longer period + runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) }) + return rf +} + +func (r *requestFinalizer) Close() error { + return r.close(false) +} + +func (r *requestFinalizer) close(asyncRelease bool) error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + runtime.SetFinalizer(r, nil) + if asyncRelease { + go r.Request.Close() + return nil + } else { + return r.Request.Close() + } + } + return nil +} diff --git a/v3/internal/assetserver/webview/request_linux.go b/v3/internal/assetserver/webview/request_linux.go new file mode 100644 index 000000000..32969a1ba --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux.go @@ -0,0 +1,83 @@ +//go:build linux +// +build linux + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +*/ +import "C" + +import ( + "io" + "net/http" + "unsafe" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request { + webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest) + C.g_object_ref(C.gpointer(webkitReq)) + + req := &request{req: webkitReq} + return newRequestFinalizer(req) +} + +var _ Request = &request{} + +type request struct { + req *C.WebKitURISchemeRequest + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) URL() (string, error) { + return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + r.body = webkit_uri_scheme_request_get_http_body(r.req) + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + C.g_object_unref(C.gpointer(r.req)) + return err +} diff --git a/v3/internal/assetserver/webview/request_linux_purego.go b/v3/internal/assetserver/webview/request_linux_purego.go new file mode 100644 index 000000000..bf724a55b --- /dev/null +++ b/v3/internal/assetserver/webview/request_linux_purego.go @@ -0,0 +1,94 @@ +//go:build linux && purego +// +build linux,purego + +package webview + +import ( + "io" + "net/http" + + "github.com/ebitengine/purego" +) + +// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` +// +// Please make sure to call Release() when finished using the request. +func NewRequest(webKitURISchemeRequest uintptr) Request { + webkitReq := webKitURISchemeRequest + req := &request{req: webkitReq} + req.AddRef() + return req +} + +var _ Request = &request{} + +type request struct { + req uintptr + + header http.Header + body io.ReadCloser + rw *responseWriter +} + +func (r *request) AddRef() error { + var objectRef func(uintptr) + purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref") + objectRef(r.req) + return nil +} + +func (r *request) Release() error { + var objectUnref func(uintptr) + purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref") + objectUnref(r.req) + return nil +} + +func (r *request) URL() (string, error) { + var getUri func(uintptr) string + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri") + return getUri(r.req), nil +} + +func (r *request) Method() (string, error) { + return webkit_uri_scheme_request_get_http_method(r.req), nil +} + +func (r *request) Header() (http.Header, error) { + if r.header != nil { + return r.header, nil + } + + r.header = webkit_uri_scheme_request_get_http_headers(r.req) + return r.header, nil +} + +func (r *request) Body() (io.ReadCloser, error) { + if r.body != nil { + return r.body, nil + } + + // WebKit2GTK has currently no support for request bodies. + r.body = http.NoBody + + return r.body, nil +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r.req} + return r.rw +} + +func (r *request) Close() error { + var err error + if r.body != nil { + err = r.body.Close() + } + r.Response().Finish() + r.Release() + return err +} diff --git a/v3/internal/assetserver/webview/request_windows.go b/v3/internal/assetserver/webview/request_windows.go new file mode 100644 index 000000000..9f68af2e1 --- /dev/null +++ b/v3/internal/assetserver/webview/request_windows.go @@ -0,0 +1,218 @@ +//go:build windows + +package webview + +import ( + "errors" + "fmt" + "io" + "net/http" + + "github.com/wailsapp/go-webview2/pkg/edge" +) + +// NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread! +func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) { + req, err := args.GetRequest() + if err != nil { + return nil, fmt.Errorf("GetRequest failed: %s", err) + } + defer req.Release() + + r := &request{ + invokeSync: invokeSync, + } + + code := http.StatusInternalServerError + r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "") + if err != nil { + return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err) + } + + if err := args.PutResponse(r.response); err != nil { + r.finishResponse() + return nil, fmt.Errorf("PutResponse failed: %s", err) + } + + r.deferral, err = args.GetDeferral() + if err != nil { + r.finishResponse() + return nil, fmt.Errorf("GetDeferral failed: %s", err) + } + + r.url, r.urlErr = req.GetUri() + r.method, r.methodErr = req.GetMethod() + r.header, r.headerErr = getHeaders(req) + + if content, err := req.GetContent(); err != nil { + r.bodyErr = err + } else if content != nil { + // It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety + r.body = &iStreamReleaseCloser{stream: content} + } + + return r, nil +} + +var _ Request = &request{} + +type request struct { + response *edge.ICoreWebView2WebResourceResponse + deferral *edge.ICoreWebView2Deferral + + url string + urlErr error + + method string + methodErr error + + header http.Header + headerErr error + + body io.ReadCloser + bodyErr error + rw *responseWriter + + invokeSync func(fn func()) +} + +func (r *request) URL() (string, error) { + return r.url, r.urlErr +} + +func (r *request) Method() (string, error) { + return r.method, r.methodErr +} + +func (r *request) Header() (http.Header, error) { + return r.header, r.headerErr +} + +func (r *request) Body() (io.ReadCloser, error) { + return r.body, r.bodyErr +} + +func (r *request) Response() ResponseWriter { + if r.rw != nil { + return r.rw + } + + r.rw = &responseWriter{req: r} + return r.rw +} + +func (r *request) Close() error { + var errs []error + if r.body != nil { + if err := r.body.Close(); err != nil { + errs = append(errs, err) + } + r.body = nil + } + + if err := r.Response().Finish(); err != nil { + errs = append(errs, err) + } + + return combineErrs(errs) +} + +// finishResponse must be called on the main-thread +func (r *request) finishResponse() error { + var errs []error + if r.response != nil { + if err := r.response.Release(); err != nil { + errs = append(errs, err) + } + r.response = nil + } + if r.deferral != nil { + if err := r.deferral.Complete(); err != nil { + errs = append(errs, err) + } + + if err := r.deferral.Release(); err != nil { + errs = append(errs, err) + } + r.deferral = nil + } + return combineErrs(errs) +} + +type iStreamReleaseCloser struct { + stream *edge.IStream + closed bool +} + +func (i *iStreamReleaseCloser) Read(p []byte) (int, error) { + if i.closed { + return 0, io.ErrClosedPipe + } + return i.stream.Read(p) +} + +func (i *iStreamReleaseCloser) Close() error { + if i.closed { + return nil + } + i.closed = true + return i.stream.Release() +} + +func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) { + header := http.Header{} + headers, err := req.GetHeaders() + if err != nil { + return nil, fmt.Errorf("GetHeaders Error: %s", err) + } + defer headers.Release() + + headersIt, err := headers.GetIterator() + if err != nil { + return nil, fmt.Errorf("GetIterator Error: %s", err) + } + defer headersIt.Release() + + for { + has, err := headersIt.HasCurrentHeader() + if err != nil { + return nil, fmt.Errorf("HasCurrentHeader Error: %s", err) + } + if !has { + break + } + + name, value, err := headersIt.GetCurrentHeader() + if err != nil { + return nil, fmt.Errorf("GetCurrentHeader Error: %s", err) + } + + header.Set(name, value) + if _, err := headersIt.MoveNext(); err != nil { + return nil, fmt.Errorf("MoveNext Error: %s", err) + } + } + + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + // So prevent 304 status codes by removing the headers that are used in combinationwith caching. + header.Del("If-Modified-Since") + header.Del("If-None-Match") + return header, nil +} + +func combineErrs(errs []error) error { + err := errors.Join(errs...) + + if err != nil { + // errors.Join wraps even a single error. + // Check the filtered error list, + // and if it has just one element return it directly. + errs = err.(interface{ Unwrap() []error }).Unwrap() + if len(errs) == 1 { + return errs[0] + } + } + + return err +} diff --git a/v3/internal/assetserver/webview/responsewriter.go b/v3/internal/assetserver/webview/responsewriter.go new file mode 100644 index 000000000..2fc7ede51 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter.go @@ -0,0 +1,28 @@ +package webview + +import ( + "errors" + "net/http" +) + +const ( + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" +) + +var ( + errRequestStopped = errors.New("request has been stopped") + errResponseFinished = errors.New("response has been finished") +) + +// A ResponseWriter interface is used by an HTTP handler to +// construct an HTTP response for the WebView. +type ResponseWriter interface { + http.ResponseWriter + + // Finish the response and flush all data. A Finish after the request has already been finished has no effect. + Finish() error + + // Code returns the HTTP status code of the response + Code() int +} diff --git a/v3/internal/assetserver/webview/responsewriter_darwin.go b/v3/internal/assetserver/webview/responsewriter_darwin.go new file mode 100644 index 000000000..499ae6f9f --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_darwin.go @@ -0,0 +1,155 @@ +//go:build darwin + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import +#import + +typedef void (^schemeTaskCaller)(id); + +static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) { + id urlSchemeTask = (id) wkUrlSchemeTask; + if (urlSchemeTask == nil) { + return false; + } + + @autoreleasepool { + @try { + fn(urlSchemeTask); + } @catch (NSException *exception) { + // This is very bad to detect a stopped schemeTask this should be implemented in a better way + // But it seems to be very tricky to not deadlock when keeping a lock curing executing fn() + // It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want + // to get the lock again to start another request or stop it. + if ([exception.reason isEqualToString: @"This task has already been stopped"]) { + return false; + } + + @throw exception; + } + + return true; + } +} + +static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsdata = [NSData dataWithBytes:data length:datalength]; + [urlSchemeTask didReceiveData:nsdata]; + }); +} + +static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + [urlSchemeTask didFinish]; + }); +} + +static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) { + return urlSchemeTaskCall( + wkUrlSchemeTask, + ^(id urlSchemeTask) { + NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength]; + NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil]; + NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease]; + + [urlSchemeTask didReceiveResponse:response]; + }); +} +*/ +import "C" + +import ( + "encoding/json" + "net/http" + "unsafe" +) + +var _ ResponseWriter = &responseWriter{} + +type responseWriter struct { + r *request + + header http.Header + wroteHeader bool + code int + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + var content unsafe.Pointer + var contentLen int + if buf != nil { + content = unsafe.Pointer(&buf[0]) + contentLen = len(buf) + } + + if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) { + return 0, errRequestStopped + } + return contentLen, nil +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + header := map[string]string{} + for k := range rw.Header() { + header[k] = rw.Header().Get(k) + } + headerData, _ := json.Marshal(header) + + var headers unsafe.Pointer + var headersLen int + if len(headerData) != 0 { + headers = unsafe.Pointer(&headerData[0]) + headersLen = len(headerData) + } + + C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen)) +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + C.URLSchemeTaskDidFinish(rw.r.task) + + return nil +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux.go b/v3/internal/assetserver/webview/responsewriter_linux.go new file mode 100644 index 000000000..169b68ab5 --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux.go @@ -0,0 +1,135 @@ +//go:build linux + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "gio/gunixinputstream.h" + +*/ +import "C" +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + "unsafe" +) + +type responseWriter struct { + req *C.WebKitURISchemeRequest + + header http.Header + wroteHeader bool + finished bool + code int + + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1)) + defer C.g_object_unref(C.gpointer(stream)) + + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } + return nil +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + msg := C.CString(err.Error()) + gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg) + C.webkit_uri_scheme_request_finish_error(rw.req, gerr) + C.g_error_free(gerr) + C.free(unsafe.Pointer(msg)) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_linux_purego.go b/v3/internal/assetserver/webview/responsewriter_linux_purego.go new file mode 100644 index 000000000..6742d1bda --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_linux_purego.go @@ -0,0 +1,180 @@ +//go:build linux && purego +// +build linux,purego + +package webview + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "syscall" + + "github.com/ebitengine/purego" +) + +const ( + gtk3 = "libgtk-3.so" + gtk4 = "libgtk-4.so" +) + +var ( + gtk uintptr + webkit uintptr + version int +) + +func init() { + var err error + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + // log.Println("Failed to open GTK4: Falling back to GTK3") + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + version = 3 + + var webkit4 string = "libwebkit2gtk-4.1.so" + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } +} + +type responseWriter struct { + req uintptr + + header http.Header + wroteHeader bool + finished bool + code int + w io.WriteCloser + wErr error +} + +func (rw *responseWriter) Code() int { + return rw.code +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + if rw.wErr != nil { + return 0, rw.wErr + } + return rw.w.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.code = code + + // TODO? Is this ever called? I don't think so! + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + contentLength := int64(-1) + if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { + if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { + contentLength = pLen + } + } + // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the + // read FD is given to the InputStream and will be closed there. + // Furthermore we especially don't want to have the FD_CLOEXEC + rFD, w, err := pipe() + if err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) + return + } + rw.w = w + + var newStream func(int, bool) uintptr + purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + stream := newStream(rFD, true) + + /* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int + purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish") + + header := rw.Header() + defer unRef(stream) + if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + } + */ + if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { + rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) + return + } +} + +func (rw *responseWriter) Finish() { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return + } + rw.finished = true + if rw.w != nil { + rw.w.Close() + } +} + +func (rw *responseWriter) finishWithError(code int, err error) { + if rw.w != nil { + rw.w.Close() + rw.w = &nopCloser{io.Discard} + } + rw.wErr = err + + var newLiteral func(uint32, string, int, string) uintptr // is this correct? + purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal") + var newQuark func(string) uintptr + purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string") + var freeError func(uintptr) + purego.RegisterLibFunc(&freeError, gtk, "g_error_free") + var finishError func(uintptr, uintptr) + purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error") + + msg := string(err.Error()) + //gquark := newQuark(msg) + gerr := newLiteral(1, msg, code, msg) + finishError(rw.req, gerr) + freeError(gerr) +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +func pipe() (r int, w *os.File, err error) { + var p [2]int + e := syscall.Pipe2(p[0:], 0) + if e != nil { + return 0, nil, fmt.Errorf("pipe2: %s", e) + } + + return p[0], os.NewFile(uintptr(p[1]), "|1"), nil +} diff --git a/v3/internal/assetserver/webview/responsewriter_windows.go b/v3/internal/assetserver/webview/responsewriter_windows.go new file mode 100644 index 000000000..c003f00bd --- /dev/null +++ b/v3/internal/assetserver/webview/responsewriter_windows.go @@ -0,0 +1,109 @@ +//go:build windows + +package webview + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "strings" +) + +var _ http.ResponseWriter = &responseWriter{} + +type responseWriter struct { + req *request + + header http.Header + wroteHeader bool + code int + body *bytes.Buffer + + finished bool +} + +func (rw *responseWriter) Header() http.Header { + if rw.header == nil { + rw.header = http.Header{} + } + return rw.header +} + +func (rw *responseWriter) Write(buf []byte) (int, error) { + if rw.finished { + return 0, errResponseFinished + } + + rw.WriteHeader(http.StatusOK) + + return rw.body.Write(buf) +} + +func (rw *responseWriter) WriteHeader(code int) { + if rw.wroteHeader || rw.finished { + return + } + rw.wroteHeader = true + + if rw.body == nil { + rw.body = &bytes.Buffer{} + } + + rw.code = code +} + +func (rw *responseWriter) Finish() error { + if !rw.wroteHeader { + rw.WriteHeader(http.StatusNotImplemented) + } + + if rw.finished { + return nil + } + rw.finished = true + + var errs []error + + code := rw.code + if code == http.StatusNotModified { + // WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other + // requests including IPC calls. + errs = append(errs, errors.New("AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError")) + code = http.StatusInternalServerError + } + + rw.req.invokeSync(func() { + resp := rw.req.response + + hdrs, err := resp.GetHeaders() + if err != nil { + errs = append(errs, fmt.Errorf("Resp.GetHeaders failed: %s", err)) + } else { + for k, v := range rw.header { + if err := hdrs.AppendHeader(k, strings.Join(v, ",")); err != nil { + errs = append(errs, fmt.Errorf("Resp.AppendHeader failed: %s", err)) + } + } + hdrs.Release() + } + + if err := resp.PutStatusCode(code); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutStatusCode failed: %s", err)) + } + + if err := resp.PutByteContent(rw.body.Bytes()); err != nil { + errs = append(errs, fmt.Errorf("Resp.PutByteContent failed: %s", err)) + } + + if err := rw.req.finishResponse(); err != nil { + errs = append(errs, fmt.Errorf("Resp.finishResponse failed: %s", err)) + } + }) + + return combineErrs(errs) +} + +func (rw *responseWriter) Code() int { + return rw.code +} diff --git a/v3/internal/assetserver/webview/webkit2.go b/v3/internal/assetserver/webview/webkit2.go new file mode 100644 index 000000000..21156a4c5 --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2.go @@ -0,0 +1,137 @@ +//go:build linux + +package webview + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 libsoup-3.0 + +#include "gtk/gtk.h" +#include "webkit2/webkit2.h" +#include "libsoup/soup.h" +*/ +import "C" + +import ( + "fmt" + "io" + "net/http" + "strings" + "unsafe" +) + +const Webkit2MinMinorVersion = 40 + +func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string { + method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req)) + return strings.ToUpper(method) +} + +func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header { + hdrs := C.webkit_uri_scheme_request_get_http_headers(req) + + var iter C.SoupMessageHeadersIter + C.soup_message_headers_iter_init(&iter, hdrs) + + var name *C.char + var value *C.char + + h := http.Header{} + for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 { + h.Add(C.GoString(name), C.GoString(value)) + } + + return h +} + +func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error { + resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength)) + defer C.g_object_unref(C.gpointer(resp)) + + cReason := C.CString(http.StatusText(code)) + C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason) + C.free(unsafe.Pointer(cReason)) + + cMimeType := C.CString(header.Get(HeaderContentType)) + C.webkit_uri_scheme_response_set_content_type(resp, cMimeType) + C.free(unsafe.Pointer(cMimeType)) + + hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + cName := C.CString(name) + for _, value := range values { + cValue := C.CString(value) + C.soup_message_headers_append(hdrs, cName, cValue) + C.free(unsafe.Pointer(cValue)) + } + C.free(unsafe.Pointer(cName)) + } + + C.webkit_uri_scheme_response_set_http_headers(resp, hdrs) + + C.webkit_uri_scheme_request_finish_with_response(req, resp) + return nil +} + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/assetserver/webview/webkit2_purego.go b/v3/internal/assetserver/webview/webkit2_purego.go new file mode 100644 index 000000000..28832c8bd --- /dev/null +++ b/v3/internal/assetserver/webview/webkit2_purego.go @@ -0,0 +1,158 @@ +//go:build linux && purego + +package webview + +import ( + "net/http" + "strings" + + "github.com/ebitengine/purego" +) + +func webkit_uri_scheme_request_get_http_method(req uintptr) string { + var getMethod func(uintptr) string + purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method") + return strings.ToUpper(getMethod(req)) +} + +func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header { + var getHeaders func(uintptr) uintptr + purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers") + + hdrs := getHeaders(req) + + var headersIterInit func(uintptr, uintptr) uintptr + purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init") + + // TODO: How do we get a struct? + /* + typedef struct { + SoupMessageHeaders *hdrs; + int index_common; + int index_uncommon; + } SoupMessageHeadersIterReal; + */ + iter := make([]byte, 12) + headersIterInit(&iter, hdrs) + + var iterNext func(uintptr, *string, *string) int + purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next") + + var name string + var value string + h := http.Header{} + + for iterNext(&iter, &name, &value) != 0 { + h.Add(name, value) + } + + return h +} + +func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { + + var newResponse func(uintptr, int64) string + purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new") + var unRef func(uintptr) + purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") + + resp := newResponse(stream, streamLength) + defer unRef(resp) + + var setStatus func(uintptr, int, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status") + + setStatus(resp, code, cReason) + + var setContentType func(uintptr, string) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type") + + setContentType(resp, header.Get(HeaderContentType)) + + soup := gtk + var soupHeadersNew func(int) uintptr + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new") + var soupHeadersAppend func(uintptr, string, string) + purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append") + + hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE) + for name, values := range header { + for _, value := range values { + soupHeadersAppend(hdrs, name, value) + } + } + + var setHttpHeaders func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers") + + setHttpHeaders(resp, hdrs) + var finishWithResponse func(uintptr, uintptr) + purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response") + finishWithResponse(req, resp) + + return nil +} + +func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { + stream := C.webkit_uri_scheme_request_get_http_body(req) + if stream == nil { + return http.NoBody + } + return &webkitRequestBody{stream: stream} +} + +type webkitRequestBody struct { + stream *C.GInputStream + closed bool +} + +// Read implements io.Reader +func (r *webkitRequestBody) Read(p []byte) (int, error) { + if r.closed { + return 0, io.ErrClosedPipe + } + + var content unsafe.Pointer + var contentLen int + if p != nil { + content = unsafe.Pointer(&p[0]) + contentLen = len(p) + } + + var n C.gsize + var gErr *C.GError + res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) + if res == 0 { + return 0, formatGError("stream read failed", gErr) + } else if n == 0 { + return 0, io.EOF + } + return int(n), nil +} + +func (r *webkitRequestBody) Close() error { + if r.closed { + return nil + } + r.closed = true + + // https://docs.gtk.org/gio/method.InputStream.close.html + // Streams will be automatically closed when the last reference is dropped, but you might want to call this function + // to make sure resources are released as early as possible. + var err error + var gErr *C.GError + if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { + err = formatGError("stream close failed", gErr) + } + C.g_object_unref(C.gpointer(r.stream)) + r.stream = nil + return err +} + +func formatGError(msg string, gErr *C.GError, args ...any) error { + if gErr != nil && gErr.message != nil { + msg += ": " + C.GoString(gErr.message) + C.g_error_free(gErr) + } + return fmt.Errorf(msg, args...) +} diff --git a/v3/internal/buildinfo/buildinfo.go b/v3/internal/buildinfo/buildinfo.go new file mode 100644 index 000000000..930831f1f --- /dev/null +++ b/v3/internal/buildinfo/buildinfo.go @@ -0,0 +1,40 @@ +package buildinfo + +import ( + "fmt" + "runtime/debug" + "slices" + + "github.com/samber/lo" +) + +type Info struct { + Development bool + Version string + BuildSettings map[string]string + wailsPackage *debug.Module +} + +func Get() (*Info, error) { + + var result Info + + // BuildInfo contains the build info for the application + var BuildInfo *debug.BuildInfo + + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return nil, fmt.Errorf("could not read build info from binary") + } + result.BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + result.Version = BuildInfo.Main.Version + result.Development = -1 != slices.IndexFunc(BuildInfo.Settings, func(setting debug.BuildSetting) bool { + return setting.Key == "vcs" && setting.Value == "git" + }) + + return &result, nil + +} diff --git a/v3/internal/buildinfo/buildinfo_test.go b/v3/internal/buildinfo/buildinfo_test.go new file mode 100644 index 000000000..ab79f63f8 --- /dev/null +++ b/v3/internal/buildinfo/buildinfo_test.go @@ -0,0 +1,13 @@ +package buildinfo + +import ( + "testing" +) + +func TestGet(t *testing.T) { + result, err := Get() + if err != nil { + t.Error(err) + } + _ = result +} diff --git a/v3/internal/capabilities/capabilities.go b/v3/internal/capabilities/capabilities.go new file mode 100644 index 000000000..af9428bb2 --- /dev/null +++ b/v3/internal/capabilities/capabilities.go @@ -0,0 +1,16 @@ +package capabilities + +import "encoding/json" + +type Capabilities struct { + HasNativeDrag bool +} + +func (c Capabilities) AsBytes() []byte { + // JSON encode + result, err := json.Marshal(c) + if err != nil { + return []byte("{}") + } + return result +} diff --git a/v3/internal/capabilities/capabilities_darwin.go b/v3/internal/capabilities/capabilities_darwin.go new file mode 100644 index 000000000..5cd0b600c --- /dev/null +++ b/v3/internal/capabilities/capabilities_darwin.go @@ -0,0 +1,9 @@ +//go:build darwin + +package capabilities + +func newCapabilities(_ string) Capabilities { + c := Capabilities{} + c.HasNativeDrag = false + return c +} diff --git a/v3/internal/capabilities/capabilities_linux.go b/v3/internal/capabilities/capabilities_linux.go new file mode 100644 index 000000000..b0debdbb0 --- /dev/null +++ b/v3/internal/capabilities/capabilities_linux.go @@ -0,0 +1,11 @@ +//go:build linux + +package capabilities + +func NewCapabilities() Capabilities { + c := Capabilities{} + // For now, assume Linux has native drag support + // TODO: Implement proper WebKit version detection + c.HasNativeDrag = true + return c +} diff --git a/v3/internal/capabilities/capabilities_windows.go b/v3/internal/capabilities/capabilities_windows.go new file mode 100644 index 000000000..78591dcf6 --- /dev/null +++ b/v3/internal/capabilities/capabilities_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package capabilities + +import "github.com/wailsapp/go-webview2/webviewloader" + +type version string + +func (v version) IsAtLeast(input string) bool { + result, err := webviewloader.CompareBrowserVersions(string(v), input) + if err != nil { + return false + } + return result >= 0 +} + +func NewCapabilities(webview2version string) Capabilities { + webview2 := version(webview2version) + c := Capabilities{} + c.HasNativeDrag = webview2.IsAtLeast("113.0.0.0") + return c +} diff --git a/v3/internal/changelog/changelog.go b/v3/internal/changelog/changelog.go new file mode 100644 index 000000000..2690ea674 --- /dev/null +++ b/v3/internal/changelog/changelog.go @@ -0,0 +1,80 @@ +package changelog + +import ( + "fmt" + "io" + "os" + "strings" +) + +// ProcessorResult contains the results of processing a changelog file +type ProcessorResult struct { + Entry *ChangelogEntry + ValidationResult ValidationResult + HasContent bool +} + +// Processor handles the complete changelog processing pipeline +type Processor struct { + parser *Parser + validator *Validator +} + +// NewProcessor creates a new changelog processor with parser and validator +func NewProcessor() *Processor { + return &Processor{ + parser: NewParser(), + validator: NewValidator(), + } +} + +// ProcessFile processes a changelog file and returns the parsed and validated entry +func (p *Processor) ProcessFile(filePath string) (*ProcessorResult, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to open changelog file %s: %w", filePath, err) + } + defer file.Close() + + return p.ProcessReader(file) +} + +// ProcessReader processes changelog content from a reader +func (p *Processor) ProcessReader(reader io.Reader) (*ProcessorResult, error) { + // Parse the content + entry, err := p.parser.ParseContent(reader) + if err != nil { + return nil, fmt.Errorf("failed to parse changelog content: %w", err) + } + + // Validate the parsed entry + validationResult := p.validator.ValidateEntry(entry) + + result := &ProcessorResult{ + Entry: entry, + ValidationResult: validationResult, + HasContent: entry.HasContent(), + } + + return result, nil +} + +// ProcessString processes changelog content from a string +func (p *Processor) ProcessString(content string) (*ProcessorResult, error) { + return p.ProcessReader(strings.NewReader(content)) +} + +// ValidateFile validates a changelog file without parsing it into an entry +func (p *Processor) ValidateFile(filePath string) (ValidationResult, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return ValidationResult{Valid: false}, fmt.Errorf("failed to read changelog file %s: %w", filePath, err) + } + + return p.validator.ValidateContent(string(content)), nil +} + +// ValidateString validates changelog content from a string +func (p *Processor) ValidateString(content string) ValidationResult { + return p.validator.ValidateContent(content) +} diff --git a/v3/internal/changelog/changelog_test.go b/v3/internal/changelog/changelog_test.go new file mode 100644 index 000000000..88a2ddbd0 --- /dev/null +++ b/v3/internal/changelog/changelog_test.go @@ -0,0 +1,296 @@ +package changelog + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestNewProcessor(t *testing.T) { + processor := NewProcessor() + if processor == nil { + t.Fatal("NewProcessor() returned nil") + } + if processor.parser == nil { + t.Error("parser not initialized") + } + if processor.validator == nil { + t.Error("validator not initialized") + } +} + +func TestProcessString_ValidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options +- Add new SetWindowIcon() method to runtime API (#1234) + +## Fixed +- Fix memory leak in event system during window close operations (#5678)` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if !result.HasContent { + t.Error("Result should have content") + } + + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got errors: %s", result.ValidationResult.GetValidationSummary()) + } + + // Check parsed content + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + if len(result.Entry.Fixed) != 1 { + t.Errorf("Expected 1 Fixed item, got %d", len(result.Entry.Fixed)) + } +} + +func TestProcessString_InvalidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Short +- TODO: add proper description + +## InvalidSection +- This section is invalid` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if result.ValidationResult.Valid { + t.Error("Validation should fail for invalid content") + } + + // The parser will parse the Added section correctly (2 items) + // The InvalidSection won't be parsed since it's not a valid section name + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + + // Should have validation errors + if len(result.ValidationResult.Errors) == 0 { + t.Error("Should have validation errors") + } +} + +func TestProcessString_EmptyContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added + + +## Changed +` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + if result.HasContent { + t.Error("Result should not have content") + } + + if result.ValidationResult.Valid { + t.Error("Validation should fail for empty content") + } +} + +func TestProcessFile_ValidFile(t *testing.T) { + processor := NewProcessor() + + // Create a temporary file + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test_changelog.md") + + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options +- Add new SetWindowIcon() method to runtime API (#1234) + +## Fixed +- Fix memory leak in event system during window close operations (#5678)` + + err := os.WriteFile(filePath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + result, err := processor.ProcessFile(filePath) + if err != nil { + t.Fatalf("ProcessFile() returned error: %v", err) + } + + if !result.HasContent { + t.Error("Result should have content") + } + + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got errors: %s", result.ValidationResult.GetValidationSummary()) + } + + // Check parsed content + if len(result.Entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(result.Entry.Added)) + } + if len(result.Entry.Fixed) != 1 { + t.Errorf("Expected 1 Fixed item, got %d", len(result.Entry.Fixed)) + } +} + +func TestProcessFile_NonexistentFile(t *testing.T) { + processor := NewProcessor() + + result, err := processor.ProcessFile("/nonexistent/file.md") + if err == nil { + t.Error("ProcessFile() should return error for nonexistent file") + } + if result != nil { + t.Error("ProcessFile() should return nil result for nonexistent file") + } +} + +func TestValidateString_ValidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options + +## Fixed +- Fix memory leak in event system during window close operations` + + result := processor.ValidateString(content) + if !result.Valid { + t.Errorf("ValidateString() should be valid, got errors: %s", result.GetValidationSummary()) + } +} + +func TestValidateString_InvalidContent(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## InvalidSection +- This section is invalid + +- Bullet point outside section` + + result := processor.ValidateString(content) + if result.Valid { + t.Error("ValidateString() should be invalid") + } + + if len(result.Errors) == 0 { + t.Error("ValidateString() should return validation errors") + } +} + +func TestValidateFile_ValidFile(t *testing.T) { + processor := NewProcessor() + + // Create a temporary file + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test_changelog.md") + + content := `# Unreleased Changes + +## Added +- Add support for custom window icons in application options + +## Fixed +- Fix memory leak in event system during window close operations` + + err := os.WriteFile(filePath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + result, err := processor.ValidateFile(filePath) + if err != nil { + t.Fatalf("ValidateFile() returned error: %v", err) + } + + if !result.Valid { + t.Errorf("ValidateFile() should be valid, got errors: %s", result.GetValidationSummary()) + } +} + +func TestValidateFile_NonexistentFile(t *testing.T) { + processor := NewProcessor() + + result, err := processor.ValidateFile("/nonexistent/file.md") + if err == nil { + t.Error("ValidateFile() should return error for nonexistent file") + } + if result.Valid { + t.Error("ValidateFile() should return invalid result for nonexistent file") + } +} + +func TestProcessorResult_Integration(t *testing.T) { + processor := NewProcessor() + content := `# Unreleased Changes + +## Added +- Add comprehensive changelog processing system +- Add validation for Keep a Changelog format compliance + +## Changed +- Update changelog workflow to use automated processing + +## Fixed +- Fix parsing issues with various markdown bullet styles +- Fix validation edge cases for empty content sections` + + result, err := processor.ProcessString(content) + if err != nil { + t.Fatalf("ProcessString() returned error: %v", err) + } + + // Test that we can format the result for different outputs + changelogFormat := result.Entry.FormatForChangelog() + if !strings.Contains(changelogFormat, "### Added") { + t.Error("FormatForChangelog() should contain Added section") + } + if !strings.Contains(changelogFormat, "### Changed") { + t.Error("FormatForChangelog() should contain Changed section") + } + if !strings.Contains(changelogFormat, "### Fixed") { + t.Error("FormatForChangelog() should contain Fixed section") + } + + releaseFormat := result.Entry.FormatForRelease() + if !strings.Contains(releaseFormat, "## โœจ Added") { + t.Error("FormatForRelease() should contain Added section with emoji") + } + if !strings.Contains(releaseFormat, "## ๐Ÿ”„ Changed") { + t.Error("FormatForRelease() should contain Changed section with emoji") + } + if !strings.Contains(releaseFormat, "## ๐Ÿ› Fixed") { + t.Error("FormatForRelease() should contain Fixed section with emoji") + } + + // Test validation summary + if !result.ValidationResult.Valid { + t.Errorf("Validation should pass, got: %s", result.ValidationResult.GetValidationSummary()) + } + + summary := result.ValidationResult.GetValidationSummary() + if !strings.Contains(summary, "Validation passed") { + t.Errorf("Validation summary should indicate success, got: %s", summary) + } +} diff --git a/v3/internal/changelog/parser.go b/v3/internal/changelog/parser.go new file mode 100644 index 000000000..3557e26a0 --- /dev/null +++ b/v3/internal/changelog/parser.go @@ -0,0 +1,239 @@ +package changelog + +import ( + "bufio" + "fmt" + "io" + "regexp" + "strings" + "time" +) + +// ChangelogEntry represents a parsed changelog entry following Keep a Changelog format +type ChangelogEntry struct { + Version string `json:"version"` + Date time.Time `json:"date"` + Added []string `json:"added"` + Changed []string `json:"changed"` + Fixed []string `json:"fixed"` + Deprecated []string `json:"deprecated"` + Removed []string `json:"removed"` + Security []string `json:"security"` +} + +// Parser handles parsing of UNRELEASED_CHANGELOG.md files +type Parser struct { + // sectionRegex matches section headers like "## Added", "## Changed", etc. + sectionRegex *regexp.Regexp + // bulletRegex matches bullet points (- or *) + bulletRegex *regexp.Regexp +} + +// NewParser creates a new changelog parser +func NewParser() *Parser { + return &Parser{ + sectionRegex: regexp.MustCompile(`^##\s+(Added|Changed|Fixed|Deprecated|Removed|Security)\s*$`), + bulletRegex: regexp.MustCompile(`^[\s]*[-*]\s+(.+)$`), + } +} + +// ParseContent parses changelog content from a reader and returns a ChangelogEntry +func (p *Parser) ParseContent(reader io.Reader) (*ChangelogEntry, error) { + entry := &ChangelogEntry{ + Added: []string{}, + Changed: []string{}, + Fixed: []string{}, + Deprecated: []string{}, + Removed: []string{}, + Security: []string{}, + } + + scanner := bufio.NewScanner(reader) + var currentSection string + var inExampleSection bool + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "") { + continue + } + + // Skip the main title + if strings.HasPrefix(line, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Check for section headers + if strings.HasPrefix(line, "##") { + if matches := p.sectionRegex.FindStringSubmatch(line); len(matches) > 1 { + currentSection = strings.ToLower(matches[1]) + } else { + // Invalid section header - reset current section + currentSection = "" + } + continue + } + + // Parse bullet points + if matches := p.bulletRegex.FindStringSubmatch(line); len(matches) > 1 { + content := strings.TrimSpace(matches[1]) + if content == "" { + continue + } + + switch currentSection { + case "added": + entry.Added = append(entry.Added, content) + case "changed": + entry.Changed = append(entry.Changed, content) + case "fixed": + entry.Fixed = append(entry.Fixed, content) + case "deprecated": + entry.Deprecated = append(entry.Deprecated, content) + case "removed": + entry.Removed = append(entry.Removed, content) + case "security": + entry.Security = append(entry.Security, content) + } + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading changelog content: %w", err) + } + + return entry, nil +} + +// HasContent checks if the changelog entry contains any actual content +func (entry *ChangelogEntry) HasContent() bool { + return len(entry.Added) > 0 || + len(entry.Changed) > 0 || + len(entry.Fixed) > 0 || + len(entry.Deprecated) > 0 || + len(entry.Removed) > 0 || + len(entry.Security) > 0 +} + +// FormatForChangelog formats the entry for insertion into the main changelog +func (entry *ChangelogEntry) FormatForChangelog() string { + var builder strings.Builder + + if len(entry.Added) > 0 { + builder.WriteString("### Added\n") + for _, item := range entry.Added { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Changed) > 0 { + builder.WriteString("### Changed\n") + for _, item := range entry.Changed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Fixed) > 0 { + builder.WriteString("### Fixed\n") + for _, item := range entry.Fixed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Deprecated) > 0 { + builder.WriteString("### Deprecated\n") + for _, item := range entry.Deprecated { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Removed) > 0 { + builder.WriteString("### Removed\n") + for _, item := range entry.Removed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Security) > 0 { + builder.WriteString("### Security\n") + for _, item := range entry.Security { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + return strings.TrimSpace(builder.String()) +} + +// FormatForRelease formats the entry for GitHub release notes +func (entry *ChangelogEntry) FormatForRelease() string { + var builder strings.Builder + + if len(entry.Added) > 0 { + builder.WriteString("## โœจ Added\n") + for _, item := range entry.Added { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Changed) > 0 { + builder.WriteString("## ๐Ÿ”„ Changed\n") + for _, item := range entry.Changed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Fixed) > 0 { + builder.WriteString("## ๐Ÿ› Fixed\n") + for _, item := range entry.Fixed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Deprecated) > 0 { + builder.WriteString("## โš ๏ธ Deprecated\n") + for _, item := range entry.Deprecated { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Removed) > 0 { + builder.WriteString("## ๐Ÿ—‘๏ธ Removed\n") + for _, item := range entry.Removed { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + if len(entry.Security) > 0 { + builder.WriteString("## ๐Ÿ”’ Security\n") + for _, item := range entry.Security { + builder.WriteString(fmt.Sprintf("- %s\n", item)) + } + builder.WriteString("\n") + } + + return strings.TrimSpace(builder.String()) +} diff --git a/v3/internal/changelog/parser_test.go b/v3/internal/changelog/parser_test.go new file mode 100644 index 000000000..fbdb35c8c --- /dev/null +++ b/v3/internal/changelog/parser_test.go @@ -0,0 +1,468 @@ +package changelog + +import ( + "strings" + "testing" +) + +func TestNewParser(t *testing.T) { + parser := NewParser() + if parser == nil { + t.Fatal("NewParser() returned nil") + } + if parser.sectionRegex == nil { + t.Error("sectionRegex not initialized") + } + if parser.bulletRegex == nil { + t.Error("bulletRegex not initialized") + } +} + +func TestParseContent_EmptyContent(t *testing.T) { + parser := NewParser() + reader := strings.NewReader("") + + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + if entry.HasContent() { + t.Error("Empty content should not have content") + } +} + +func TestParseContent_OnlyComments(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + + + + +## Added + + +## Changed +` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + if entry.HasContent() { + t.Error("Content with only comments should not have content") + } +} + +func TestParseContent_BasicSections(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- New feature A +- New feature B + +## Changed +- Changed feature C + +## Fixed +- Fixed bug D +- Fixed bug E + +## Deprecated +- Deprecated feature F + +## Removed +- Removed feature G + +## Security +- Security fix H` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Test Added section + if len(entry.Added) != 2 { + t.Errorf("Expected 2 Added items, got %d", len(entry.Added)) + } + if entry.Added[0] != "New feature A" { + t.Errorf("Expected 'New feature A', got '%s'", entry.Added[0]) + } + if entry.Added[1] != "New feature B" { + t.Errorf("Expected 'New feature B', got '%s'", entry.Added[1]) + } + + // Test Changed section + if len(entry.Changed) != 1 { + t.Errorf("Expected 1 Changed item, got %d", len(entry.Changed)) + } + if entry.Changed[0] != "Changed feature C" { + t.Errorf("Expected 'Changed feature C', got '%s'", entry.Changed[0]) + } + + // Test Fixed section + if len(entry.Fixed) != 2 { + t.Errorf("Expected 2 Fixed items, got %d", len(entry.Fixed)) + } + if entry.Fixed[0] != "Fixed bug D" { + t.Errorf("Expected 'Fixed bug D', got '%s'", entry.Fixed[0]) + } + if entry.Fixed[1] != "Fixed bug E" { + t.Errorf("Expected 'Fixed bug E', got '%s'", entry.Fixed[1]) + } + + // Test Deprecated section + if len(entry.Deprecated) != 1 { + t.Errorf("Expected 1 Deprecated item, got %d", len(entry.Deprecated)) + } + if entry.Deprecated[0] != "Deprecated feature F" { + t.Errorf("Expected 'Deprecated feature F', got '%s'", entry.Deprecated[0]) + } + + // Test Removed section + if len(entry.Removed) != 1 { + t.Errorf("Expected 1 Removed item, got %d", len(entry.Removed)) + } + if entry.Removed[0] != "Removed feature G" { + t.Errorf("Expected 'Removed feature G', got '%s'", entry.Removed[0]) + } + + // Test Security section + if len(entry.Security) != 1 { + t.Errorf("Expected 1 Security item, got %d", len(entry.Security)) + } + if entry.Security[0] != "Security fix H" { + t.Errorf("Expected 'Security fix H', got '%s'", entry.Security[0]) + } + + // Test HasContent + if !entry.HasContent() { + t.Error("Entry should have content") + } +} + +func TestParseContent_WithExampleSection(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Real feature A + +## Changed +- Real change B + +--- + +### Example Entries: + +**Added:** +- Example feature that should be ignored +- Another example that should be ignored + +**Fixed:** +- Example fix that should be ignored` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Should only have the real entries, not the examples + if len(entry.Added) != 1 { + t.Errorf("Expected 1 Added item, got %d", len(entry.Added)) + } + if entry.Added[0] != "Real feature A" { + t.Errorf("Expected 'Real feature A', got '%s'", entry.Added[0]) + } + + if len(entry.Changed) != 1 { + t.Errorf("Expected 1 Changed item, got %d", len(entry.Changed)) + } + if entry.Changed[0] != "Real change B" { + t.Errorf("Expected 'Real change B', got '%s'", entry.Changed[0]) + } + + // Should not have any Fixed items from examples + if len(entry.Fixed) != 0 { + t.Errorf("Expected 0 Fixed items, got %d", len(entry.Fixed)) + } +} + +func TestParseContent_DifferentBulletStyles(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Feature with dash +* Feature with asterisk + - Indented feature with dash + * Indented feature with asterisk + +## Fixed +- Feature with extra spaces +* Another with extra spaces` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + expectedAdded := []string{ + "Feature with dash", + "Feature with asterisk", + "Indented feature with dash", + "Indented feature with asterisk", + } + + if len(entry.Added) != len(expectedAdded) { + t.Errorf("Expected %d Added items, got %d", len(expectedAdded), len(entry.Added)) + } + + for i, expected := range expectedAdded { + if i >= len(entry.Added) || entry.Added[i] != expected { + t.Errorf("Expected Added[%d] to be '%s', got '%s'", i, expected, entry.Added[i]) + } + } + + expectedFixed := []string{ + "Feature with extra spaces", + "Another with extra spaces", + } + + if len(entry.Fixed) != len(expectedFixed) { + t.Errorf("Expected %d Fixed items, got %d", len(expectedFixed), len(entry.Fixed)) + } + + for i, expected := range expectedFixed { + if i >= len(entry.Fixed) || entry.Fixed[i] != expected { + t.Errorf("Expected Fixed[%d] to be '%s', got '%s'", i, expected, entry.Fixed[i]) + } + } +} + +func TestParseContent_EmptyBulletPoints(t *testing.T) { + parser := NewParser() + content := `# Unreleased Changes + +## Added +- Valid feature +- +- +- Another valid feature + +## Fixed +- +- Valid fix` + + reader := strings.NewReader(content) + entry, err := parser.ParseContent(reader) + if err != nil { + t.Fatalf("ParseContent() returned error: %v", err) + } + + // Should skip empty bullet points + expectedAdded := []string{ + "Valid feature", + "Another valid feature", + } + + if len(entry.Added) != len(expectedAdded) { + t.Errorf("Expected %d Added items, got %d", len(expectedAdded), len(entry.Added)) + } + + for i, expected := range expectedAdded { + if i >= len(entry.Added) || entry.Added[i] != expected { + t.Errorf("Expected Added[%d] to be '%s', got '%s'", i, expected, entry.Added[i]) + } + } + + expectedFixed := []string{"Valid fix"} + if len(entry.Fixed) != len(expectedFixed) { + t.Errorf("Expected %d Fixed items, got %d", len(expectedFixed), len(entry.Fixed)) + } + if entry.Fixed[0] != "Valid fix" { + t.Errorf("Expected 'Valid fix', got '%s'", entry.Fixed[0]) + } +} + +func TestHasContent(t *testing.T) { + tests := []struct { + name string + entry ChangelogEntry + expected bool + }{ + { + name: "Empty entry", + entry: ChangelogEntry{}, + expected: false, + }, + { + name: "Entry with Added items", + entry: ChangelogEntry{ + Added: []string{"Feature A"}, + }, + expected: true, + }, + { + name: "Entry with Changed items", + entry: ChangelogEntry{ + Changed: []string{"Change A"}, + }, + expected: true, + }, + { + name: "Entry with Fixed items", + entry: ChangelogEntry{ + Fixed: []string{"Fix A"}, + }, + expected: true, + }, + { + name: "Entry with Deprecated items", + entry: ChangelogEntry{ + Deprecated: []string{"Deprecated A"}, + }, + expected: true, + }, + { + name: "Entry with Removed items", + entry: ChangelogEntry{ + Removed: []string{"Removed A"}, + }, + expected: true, + }, + { + name: "Entry with Security items", + entry: ChangelogEntry{ + Security: []string{"Security A"}, + }, + expected: true, + }, + { + name: "Entry with multiple sections", + entry: ChangelogEntry{ + Added: []string{"Feature A"}, + Fixed: []string{"Fix A"}, + Changed: []string{"Change A"}, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.entry.HasContent(); got != tt.expected { + t.Errorf("HasContent() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestFormatForChangelog(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A", "New feature B"}, + Changed: []string{"Changed feature C"}, + Fixed: []string{"Fixed bug D"}, + Deprecated: []string{"Deprecated feature E"}, + Removed: []string{"Removed feature F"}, + Security: []string{"Security fix G"}, + } + + result := entry.FormatForChangelog() + + expected := `### Added +- New feature A +- New feature B + +### Changed +- Changed feature C + +### Fixed +- Fixed bug D + +### Deprecated +- Deprecated feature E + +### Removed +- Removed feature F + +### Security +- Security fix G` + + if result != expected { + t.Errorf("FormatForChangelog() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForChangelog_PartialSections(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A"}, + Fixed: []string{"Fixed bug B"}, + // Other sections empty + } + + result := entry.FormatForChangelog() + + expected := `### Added +- New feature A + +### Fixed +- Fixed bug B` + + if result != expected { + t.Errorf("FormatForChangelog() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForRelease(t *testing.T) { + entry := ChangelogEntry{ + Added: []string{"New feature A", "New feature B"}, + Changed: []string{"Changed feature C"}, + Fixed: []string{"Fixed bug D"}, + Deprecated: []string{"Deprecated feature E"}, + Removed: []string{"Removed feature F"}, + Security: []string{"Security fix G"}, + } + + result := entry.FormatForRelease() + + expected := `## โœจ Added +- New feature A +- New feature B + +## ๐Ÿ”„ Changed +- Changed feature C + +## ๐Ÿ› Fixed +- Fixed bug D + +## โš ๏ธ Deprecated +- Deprecated feature E + +## ๐Ÿ—‘๏ธ Removed +- Removed feature F + +## ๐Ÿ”’ Security +- Security fix G` + + if result != expected { + t.Errorf("FormatForRelease() mismatch.\nExpected:\n%s\n\nGot:\n%s", expected, result) + } +} + +func TestFormatForRelease_EmptyEntry(t *testing.T) { + entry := ChangelogEntry{} + + result := entry.FormatForRelease() + + if result != "" { + t.Errorf("FormatForRelease() for empty entry should return empty string, got: %s", result) + } +} diff --git a/v3/internal/changelog/validator.go b/v3/internal/changelog/validator.go new file mode 100644 index 000000000..20bff9ac3 --- /dev/null +++ b/v3/internal/changelog/validator.go @@ -0,0 +1,316 @@ +package changelog + +import ( + "fmt" + "regexp" + "strings" +) + +// ValidationError represents a validation error with context +type ValidationError struct { + Field string + Message string + Line int +} + +func (e ValidationError) Error() string { + if e.Line > 0 { + return fmt.Sprintf("validation error at line %d in %s: %s", e.Line, e.Field, e.Message) + } + return fmt.Sprintf("validation error in %s: %s", e.Field, e.Message) +} + +// ValidationResult contains the results of validation +type ValidationResult struct { + Valid bool + Errors []ValidationError +} + +// Validator handles validation of changelog content and entries +type Validator struct { + // Regex patterns for validation + sectionHeaderRegex *regexp.Regexp + bulletPointRegex *regexp.Regexp + urlRegex *regexp.Regexp + issueRefRegex *regexp.Regexp +} + +// NewValidator creates a new changelog validator +func NewValidator() *Validator { + return &Validator{ + sectionHeaderRegex: regexp.MustCompile(`^##\s+(Added|Changed|Fixed|Deprecated|Removed|Security)\s*$`), + bulletPointRegex: regexp.MustCompile(`^[\s]*[-*]\s+(.+)$`), + urlRegex: regexp.MustCompile(`https?://[^\s]+`), + issueRefRegex: regexp.MustCompile(`#\d+`), + } +} + +// ValidateContent validates raw changelog content for proper formatting +func (v *Validator) ValidateContent(content string) ValidationResult { + result := ValidationResult{ + Valid: true, + Errors: []ValidationError{}, + } + + lines := strings.Split(content, "\n") + var currentSection string + var hasValidSections bool + var inExampleSection bool + lineNum := 0 + + for _, line := range lines { + lineNum++ + trimmedLine := strings.TrimSpace(line) + + // Skip empty lines and comments + if trimmedLine == "" || strings.HasPrefix(trimmedLine, "") { + continue + } + + // Skip the main title + if strings.HasPrefix(trimmedLine, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(trimmedLine, "---") || strings.HasPrefix(trimmedLine, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Check for section headers + if strings.HasPrefix(trimmedLine, "##") { + if matches := v.sectionHeaderRegex.FindStringSubmatch(trimmedLine); len(matches) > 1 { + currentSection = strings.ToLower(matches[1]) + hasValidSections = true + } else { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "section_header", + Message: fmt.Sprintf("invalid section header format: '%s'. Expected format: '## SectionName'", trimmedLine), + Line: lineNum, + }) + } + continue + } + + // Check bullet points + if strings.HasPrefix(trimmedLine, "-") || strings.HasPrefix(trimmedLine, "*") { + if currentSection == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "bullet point found outside of any section", + Line: lineNum, + }) + continue + } + + // Check for empty bullet points first (just "-" or "*" with optional whitespace) + if trimmedLine == "-" || trimmedLine == "*" || strings.TrimSpace(trimmedLine[1:]) == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "empty bullet point content", + Line: lineNum, + }) + continue + } + + if matches := v.bulletPointRegex.FindStringSubmatch(trimmedLine); len(matches) > 1 { + content := strings.TrimSpace(matches[1]) + if content == "" { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: "empty bullet point content", + Line: lineNum, + }) + } else { + // Validate bullet point content + v.validateBulletPointContent(content, lineNum, &result) + } + } else { + result.Valid = false + result.Errors = append(result.Errors, ValidationError{ + Field: "bullet_point", + Message: fmt.Sprintf("malformed bullet point: '%s'", trimmedLine), + Line: lineNum, + }) + } + continue + } + + // Check for unexpected content + if trimmedLine != "" && !strings.HasPrefix(trimmedLine, " + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/v3/internal/commands/update_cli.go b/v3/internal/commands/update_cli.go new file mode 100644 index 000000000..a7f51796f --- /dev/null +++ b/v3/internal/commands/update_cli.go @@ -0,0 +1,178 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/debug" + "github.com/wailsapp/wails/v3/internal/github" + "github.com/wailsapp/wails/v3/internal/term" + "github.com/wailsapp/wails/v3/internal/version" +) + +type UpdateCLIOptions struct { + NoColour bool `name:"n" description:"Disable colour output"` + PreRelease bool `name:"pre" description:"Update to the latest pre-release (eg beta)"` + Version string `name:"version" description:"Update to a specific version (eg v3.0.0)"` + Latest bool `name:"latest" description:"Install the latest stable release"` +} + +func UpdateCLI(options *UpdateCLIOptions) error { + if options.NoColour { + term.DisableColor() + } + + term.Header("Update CLI") + + // Check if this CLI has been installed from vcs + if debug.LocalModulePath != "" && !options.Latest { + v3Path := filepath.ToSlash(debug.LocalModulePath + "/v3") + term.Println("This Wails CLI has been installed from source. To update to the latest stable release, run the following commands in the `" + v3Path + "` directory:") + term.Println(" - git pull") + term.Println(" - wails3 task install") + term.Println("") + term.Println("If you want to install the latest release, please run `wails3 update cli -latest`") + return nil + } + + if options.Latest { + latestVersion, err := github.GetLatestStableRelease() + if err != nil { + return err + } + return updateToVersion(latestVersion, true, version.String()) + } + + term.Println("Checking for updates...") + + var desiredVersion *github.SemanticVersion + var err error + var valid bool + + if len(options.Version) > 0 { + // Check if this is a valid version + valid, err = github.IsValidTag(options.Version) + if err == nil { + if !valid { + err = fmt.Errorf("version '%s' is invalid", options.Version) + } else { + desiredVersion, err = github.NewSemanticVersion(options.Version) + } + } + } else { + if options.PreRelease { + desiredVersion, err = github.GetLatestPreRelease() + } else { + desiredVersion, err = github.GetLatestStableRelease() + if err != nil { + pterm.Println("") + pterm.Println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:") + pterm.Println(" wails3 update cli -pre") + return nil + } + } + } + if err != nil { + return err + } + pterm.Println() + + currentVersion := version.String() + pterm.Printf(" Current Version : %s\n", currentVersion) + + if len(options.Version) > 0 { + fmt.Printf(" Desired Version : v%s\n", desiredVersion) + } else { + if options.PreRelease { + fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion) + } else { + fmt.Printf(" Latest Release : v%s\n", desiredVersion) + } + } + + return updateToVersion(desiredVersion, len(options.Version) > 0, currentVersion) +} + +func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error { + targetVersionString := "v" + targetVersion.String() + + if targetVersionString == currentVersion { + pterm.Println("\nLooks like you're up to date!") + return nil + } + + var desiredVersion string + + if !force { + compareVersion := currentVersion + + currentVersion, err := github.NewSemanticVersion(compareVersion) + if err != nil { + return err + } + + var success bool + + // Release -> Pre-Release = Massage current version to prerelease format + if targetVersion.IsPreRelease() && currentVersion.IsRelease() { + testVersion, err := github.NewSemanticVersion(compareVersion + "-0") + if err != nil { + return err + } + success, _ = targetVersion.IsGreaterThan(testVersion) + } + // Pre-Release -> Release = Massage target version to prerelease format + if targetVersion.IsRelease() && currentVersion.IsPreRelease() { + mainversion := currentVersion.MainVersion() + targetVersion, err = github.NewSemanticVersion(targetVersion.String()) + if err != nil { + return err + } + success, _ = targetVersion.IsGreaterThanOrEqual(mainversion) + } + + // Release -> Release = Standard check + if (targetVersion.IsRelease() && currentVersion.IsRelease()) || + (targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) { + success, _ = targetVersion.IsGreaterThan(currentVersion) + } + + // Compare + if !success { + pterm.Println("Error: The requested version is lower than the current version.") + pterm.Printf("If this is what you really want to do, use `wails3 update cli -version %s`\n", targetVersionString) + return nil + } + + desiredVersion = "v" + targetVersion.String() + } else { + desiredVersion = "v" + targetVersion.String() + } + + pterm.Println() + pterm.Print("Installing Wails CLI " + desiredVersion + "...") + + // Run command in non module directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("cannot find home directory: %w", err) + } + + cmd := exec.Command("go", "install", "github.com/wailsapp/wails/v3/cmd/wails@"+desiredVersion) + cmd.Dir = homeDir + sout, serr := cmd.CombinedOutput() + if err := cmd.Run(); err != nil { + pterm.Println("Failed.") + pterm.Error.Println(string(sout) + "\n" + serr.Error()) + return err + } + pterm.Println("Done.") + pterm.Println("\nMake sure you update your project go.mod file to use " + desiredVersion + ":") + pterm.Println(" require github.com/wailsapp/wails/v3 " + desiredVersion) + pterm.Println("\nTo view the release notes, please run `wails3 releasenotes`") + + return nil +} diff --git a/v3/internal/commands/version.go b/v3/internal/commands/version.go new file mode 100644 index 000000000..65bf66113 --- /dev/null +++ b/v3/internal/commands/version.go @@ -0,0 +1,14 @@ +package commands + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/version" +) + +type VersionOptions struct{} + +func Version(_ *VersionOptions) error { + DisableFooter = true + println(version.String()) + return nil +} diff --git a/v3/internal/commands/watcher.go b/v3/internal/commands/watcher.go new file mode 100644 index 000000000..6cdd6dc3a --- /dev/null +++ b/v3/internal/commands/watcher.go @@ -0,0 +1,51 @@ +package commands + +import ( + "github.com/atterpac/refresh/engine" + "github.com/wailsapp/wails/v3/internal/signal" + "gopkg.in/yaml.v3" + "os" +) + +type WatcherOptions struct { + Config string `description:"The config file including path" default:"."` +} + +func Watcher(options *WatcherOptions) error { + stopChan := make(chan struct{}) + + // Parse the config file + type devConfig struct { + Config engine.Config `yaml:"dev_mode"` + } + + var devconfig devConfig + + // Parse the config file + c, err := os.ReadFile(options.Config) + if err != nil { + return err + } + err = yaml.Unmarshal(c, &devconfig) + if err != nil { + return err + } + + watcherEngine, err := engine.NewEngineFromConfig(devconfig.Config) + if err != nil { + return err + } + signalHandler := signal.NewSignalHandler(func() { + stopChan <- struct{}{} + }) + signalHandler.ExitMessage = func(sig os.Signal) string { + return "" + } + signalHandler.Start() + err = watcherEngine.Start() + if err != nil { + return err + } + <-stopChan + return nil +} diff --git a/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe b/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe new file mode 100644 index 000000000..89a56ec16 Binary files /dev/null and b/v3/internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe differ diff --git a/v3/internal/dbus/DbusMenu.xml b/v3/internal/dbus/DbusMenu.xml new file mode 100644 index 000000000..db6959845 --- /dev/null +++ b/v3/internal/dbus/DbusMenu.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/README.md b/v3/internal/dbus/README.md new file mode 100644 index 000000000..d2cfce105 --- /dev/null +++ b/v3/internal/dbus/README.md @@ -0,0 +1,5 @@ +//Note that you need to have github.com/knightpp/dbus-codegen-go installed from "custom" branch + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output internal/generated/notifier/status_notifier_item.go internal/StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output internal/generated/menu/dbus_menu.go internal/DbusMenu.xml + diff --git a/v3/internal/dbus/StatusNotifierItem.xml b/v3/internal/dbus/StatusNotifierItem.xml new file mode 100644 index 000000000..1093d3d18 --- /dev/null +++ b/v3/internal/dbus/StatusNotifierItem.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v3/internal/dbus/dbus.go b/v3/internal/dbus/dbus.go new file mode 100644 index 000000000..4e4a3adbd --- /dev/null +++ b/v3/internal/dbus/dbus.go @@ -0,0 +1,4 @@ +package dbus + +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output notifier/status_notifier_item.go StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output menu/dbus_menu.go DbusMenu.xml diff --git a/v3/internal/dbus/generate.sh b/v3/internal/dbus/generate.sh new file mode 100755 index 000000000..03716e522 --- /dev/null +++ b/v3/internal/dbus/generate.sh @@ -0,0 +1,2 @@ +dbus-codegen-go -prefix com.canonical -package menu -output dbus_menu.go DbusMenu.xml +dbus-codegen-go -prefix org.kde -package notifier -output status_notifier_item.go StatusNotifierItem.xml diff --git a/v3/internal/dbus/menu/.keep b/v3/internal/dbus/menu/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/menu/dbus_menu.go b/v3/internal/dbus/menu/dbus_menu.go new file mode 100644 index 000000000..cbf4fd320 --- /dev/null +++ b/v3/internal/dbus/menu/dbus_menu.go @@ -0,0 +1,483 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package menu + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for com.canonical.dbusmenu + IntrospectDataDbusmenu = introspect.Interface{ + Name: "com.canonical.dbusmenu", + Methods: []introspect.Method{{Name: "GetLayout", Args: []introspect.Arg{ + {Name: "parentId", Type: "i", Direction: "in"}, + {Name: "recursionDepth", Type: "i", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "layout", Type: "(ia{sv}av)", Direction: "out"}, + }}, + {Name: "GetGroupProperties", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "properties", Type: "a(ia{sv})", Direction: "out"}, + }}, + {Name: "GetProperty", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "name", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "out"}, + }}, + {Name: "Event", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "eventId", Type: "s", Direction: "in"}, + {Name: "data", Type: "v", Direction: "in"}, + {Name: "timestamp", Type: "u", Direction: "in"}, + }}, + {Name: "EventGroup", Args: []introspect.Arg{ + {Name: "events", Type: "a(isvu)", Direction: "in"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + {Name: "AboutToShow", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "needUpdate", Type: "b", Direction: "out"}, + }}, + {Name: "AboutToShowGroup", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "updatesNeeded", Type: "ai", Direction: "out"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + }, + Signals: []introspect.Signal{{Name: "ItemsPropertiesUpdated", Args: []introspect.Arg{ + {Name: "updatedProps", Type: "a(ia{sv})", Direction: "out"}, + {Name: "removedProps", Type: "a(ias)", Direction: "out"}, + }}, + {Name: "LayoutUpdated", Args: []introspect.Arg{ + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "parent", Type: "i", Direction: "out"}, + }}, + {Name: "ItemActivationRequested", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "out"}, + {Name: "timestamp", Type: "u", Direction: "out"}, + }}, + }, + Properties: []introspect.Property{{Name: "Version", Type: "u", Access: "read"}, + {Name: "TextDirection", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "IconThemePath", Type: "as", Access: "read"}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceDbusmenu + "." + "ItemsPropertiesUpdated": + v0, ok := signal.Body[0].([]struct { + V0 int32 + V1 map[string]dbus.Variant + }) + if !ok { + return nil, fmt.Errorf("prop .UpdatedProps is %T, not []struct {V0 int32;V1 map[string]dbus.Variant}", signal.Body[0]) + } + v1, ok := signal.Body[1].([]struct { + V0 int32 + V1 []string + }) + if !ok { + return nil, fmt.Errorf("prop .RemovedProps is %T, not []struct {V0 int32;V1 []string}", signal.Body[1]) + } + return &Dbusmenu_ItemsPropertiesUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemsPropertiesUpdatedSignalBody{ + UpdatedProps: v0, + RemovedProps: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "LayoutUpdated": + v0, ok := signal.Body[0].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Revision is %T, not uint32", signal.Body[0]) + } + v1, ok := signal.Body[1].(int32) + if !ok { + return nil, fmt.Errorf("prop .Parent is %T, not int32", signal.Body[1]) + } + return &Dbusmenu_LayoutUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_LayoutUpdatedSignalBody{ + Revision: v0, + Parent: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "ItemActivationRequested": + v0, ok := signal.Body[0].(int32) + if !ok { + return nil, fmt.Errorf("prop .Id is %T, not int32", signal.Body[0]) + } + v1, ok := signal.Body[1].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Timestamp is %T, not uint32", signal.Body[1]) + } + return &Dbusmenu_ItemActivationRequestedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemActivationRequestedSignalBody{ + Id: v0, + Timestamp: v1, + }, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceDbusmenu = "com.canonical.dbusmenu" +) + +// Dbusmenuer is com.canonical.dbusmenu interface. +type Dbusmenuer interface { + // GetLayout is com.canonical.dbusmenu.GetLayout method. + GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant + }, err *dbus.Error) + // GetGroupProperties is com.canonical.dbusmenu.GetGroupProperties method. + GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant + }, err *dbus.Error) + // GetProperty is com.canonical.dbusmenu.GetProperty method. + GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) + // Event is com.canonical.dbusmenu.Event method. + Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) + // EventGroup is com.canonical.dbusmenu.EventGroup method. + EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 + }) (idErrors []int32, err *dbus.Error) + // AboutToShow is com.canonical.dbusmenu.AboutToShow method. + AboutToShow(id int32) (needUpdate bool, err *dbus.Error) + // AboutToShowGroup is com.canonical.dbusmenu.AboutToShowGroup method. + AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) +} + +// ExportDbusmenu exports the given object that implements com.canonical.dbusmenu on the bus. +func ExportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath, v Dbusmenuer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "GetLayout": v.GetLayout, + "GetGroupProperties": v.GetGroupProperties, + "GetProperty": v.GetProperty, + "Event": v.Event, + "EventGroup": v.EventGroup, + "AboutToShow": v.AboutToShow, + "AboutToShowGroup": v.AboutToShowGroup, + }, path, InterfaceDbusmenu) +} + +// UnexportDbusmenu unexports com.canonical.dbusmenu interface on the named path. +func UnexportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceDbusmenu) +} + +// UnimplementedDbusmenu can be embedded to have forward compatible server implementations. +type UnimplementedDbusmenu struct{} + +func (*UnimplementedDbusmenu) iface() string { + return InterfaceDbusmenu +} + +func (*UnimplementedDbusmenu) GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewDbusmenu creates and allocates com.canonical.dbusmenu. +func NewDbusmenu(object dbus.BusObject) *Dbusmenu { + return &Dbusmenu{object} +} + +// Dbusmenu implements com.canonical.dbusmenu D-Bus interface. +type Dbusmenu struct { + object dbus.BusObject +} + +// GetLayout calls com.canonical.dbusmenu.GetLayout method. +func (o *Dbusmenu) GetLayout(ctx context.Context, parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetLayout", 0, parentId, recursionDepth, propertyNames).Store(&revision, &layout) + return +} + +// GetGroupProperties calls com.canonical.dbusmenu.GetGroupProperties method. +func (o *Dbusmenu) GetGroupProperties(ctx context.Context, ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetGroupProperties", 0, ids, propertyNames).Store(&properties) + return +} + +// GetProperty calls com.canonical.dbusmenu.GetProperty method. +func (o *Dbusmenu) GetProperty(ctx context.Context, id int32, name string) (value dbus.Variant, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetProperty", 0, id, name).Store(&value) + return +} + +// Event calls com.canonical.dbusmenu.Event method. +func (o *Dbusmenu) Event(ctx context.Context, id int32, eventId string, data dbus.Variant, timestamp uint32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".Event", 0, id, eventId, data, timestamp).Store() + return +} + +// EventGroup calls com.canonical.dbusmenu.EventGroup method. +func (o *Dbusmenu) EventGroup(ctx context.Context, events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".EventGroup", 0, events).Store(&idErrors) + return +} + +// AboutToShow calls com.canonical.dbusmenu.AboutToShow method. +func (o *Dbusmenu) AboutToShow(ctx context.Context, id int32) (needUpdate bool, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShow", 0, id).Store(&needUpdate) + return +} + +// AboutToShowGroup calls com.canonical.dbusmenu.AboutToShowGroup method. +func (o *Dbusmenu) AboutToShowGroup(ctx context.Context, ids []int32) (updatesNeeded []int32, idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShowGroup", 0, ids).Store(&updatesNeeded, &idErrors) + return +} + +// GetVersion gets com.canonical.dbusmenu.Version property. +func (o *Dbusmenu) GetVersion(ctx context.Context) (version uint32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Version").Store(&version) + return +} + +// GetTextDirection gets com.canonical.dbusmenu.TextDirection property. +func (o *Dbusmenu) GetTextDirection(ctx context.Context) (textDirection string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "TextDirection").Store(&textDirection) + return +} + +// GetStatus gets com.canonical.dbusmenu.Status property. +func (o *Dbusmenu) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Status").Store(&status) + return +} + +// GetIconThemePath gets com.canonical.dbusmenu.IconThemePath property. +func (o *Dbusmenu) GetIconThemePath(ctx context.Context) (iconThemePath []string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "IconThemePath").Store(&iconThemePath) + return +} + +// Dbusmenu_ItemsPropertiesUpdatedSignal represents com.canonical.dbusmenu.ItemsPropertiesUpdated signal. +type Dbusmenu_ItemsPropertiesUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemsPropertiesUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Name() string { + return "ItemsPropertiesUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.UpdatedProps, s.Body.RemovedProps} +} + +// Dbusmenu_ItemsPropertiesUpdatedSignalBody is body container. +type Dbusmenu_ItemsPropertiesUpdatedSignalBody struct { + UpdatedProps []struct { + V0 int32 + V1 map[string]dbus.Variant + } + RemovedProps []struct { + V0 int32 + V1 []string + } +} + +// Dbusmenu_LayoutUpdatedSignal represents com.canonical.dbusmenu.LayoutUpdated signal. +type Dbusmenu_LayoutUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_LayoutUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_LayoutUpdatedSignal) Name() string { + return "LayoutUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_LayoutUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_LayoutUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_LayoutUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_LayoutUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.Revision, s.Body.Parent} +} + +// Dbusmenu_LayoutUpdatedSignalBody is body container. +type Dbusmenu_LayoutUpdatedSignalBody struct { + Revision uint32 + Parent int32 +} + +// Dbusmenu_ItemActivationRequestedSignal represents com.canonical.dbusmenu.ItemActivationRequested signal. +type Dbusmenu_ItemActivationRequestedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemActivationRequestedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Name() string { + return "ItemActivationRequested" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemActivationRequestedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) values() []interface{} { + return []interface{}{s.Body.Id, s.Body.Timestamp} +} + +// Dbusmenu_ItemActivationRequestedSignalBody is body container. +type Dbusmenu_ItemActivationRequestedSignalBody struct { + Id int32 + Timestamp uint32 +} diff --git a/v3/internal/dbus/notifier/.keep b/v3/internal/dbus/notifier/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/internal/dbus/notifier/status_notifier_item.go b/v3/internal/dbus/notifier/status_notifier_item.go new file mode 100644 index 000000000..998916440 --- /dev/null +++ b/v3/internal/dbus/notifier/status_notifier_item.go @@ -0,0 +1,636 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package notifier + +import ( + "context" + "errors" + "fmt" + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for org.kde.StatusNotifierItem + IntrospectDataStatusNotifierItem = introspect.Interface{ + Name: "org.kde.StatusNotifierItem", + Methods: []introspect.Method{{Name: "ContextMenu", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Activate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "SecondaryActivate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Scroll", Args: []introspect.Arg{ + {Name: "delta", Type: "i", Direction: "in"}, + {Name: "orientation", Type: "s", Direction: "in"}, + }}, + }, + Signals: []introspect.Signal{{Name: "NewTitle"}, + {Name: "NewIcon"}, + {Name: "NewAttentionIcon"}, + {Name: "NewOverlayIcon"}, + {Name: "NewStatus", Args: []introspect.Arg{ + {Name: "status", Type: "s", Direction: ""}, + }}, + {Name: "NewIconThemePath", Args: []introspect.Arg{ + {Name: "icon_theme_path", Type: "s", Direction: "out"}, + }}, + {Name: "NewMenu"}, + }, + Properties: []introspect.Property{{Name: "Category", Type: "s", Access: "read"}, + {Name: "Id", Type: "s", Access: "read"}, + {Name: "Title", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "WindowId", Type: "i", Access: "read"}, + {Name: "IconThemePath", Type: "s", Access: "read"}, + {Name: "Menu", Type: "o", Access: "read"}, + {Name: "ItemIsMenu", Type: "b", Access: "read"}, + {Name: "IconName", Type: "s", Access: "read"}, + {Name: "IconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "OverlayIconName", Type: "s", Access: "read"}, + {Name: "OverlayIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionIconName", Type: "s", Access: "read"}, + {Name: "AttentionIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionMovieName", Type: "s", Access: "read"}, + {Name: "ToolTip", Type: "(sa(iiay)ss)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusToolTipStruct"}, + }}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceStatusNotifierItem + "." + "NewTitle": + return &StatusNotifierItem_NewTitleSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewTitleSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIcon": + return &StatusNotifierItem_NewIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewAttentionIcon": + return &StatusNotifierItem_NewAttentionIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewAttentionIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewOverlayIcon": + return &StatusNotifierItem_NewOverlayIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewOverlayIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewStatus": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .Status is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewStatusSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewStatusSignalBody{ + Status: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIconThemePath": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .IconThemePath is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewIconThemePathSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconThemePathSignalBody{ + IconThemePath: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewMenu": + return &StatusNotifierItem_NewMenuSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewMenuSignalBody{}, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceStatusNotifierItem = "org.kde.StatusNotifierItem" +) + +// StatusNotifierItemer is org.kde.StatusNotifierItem interface. +type StatusNotifierItemer interface { + // ContextMenu is org.kde.StatusNotifierItem.ContextMenu method. + ContextMenu(x int32, y int32) (err *dbus.Error) + // Activate is org.kde.StatusNotifierItem.Activate method. + Activate(x int32, y int32) (err *dbus.Error) + // SecondaryActivate is org.kde.StatusNotifierItem.SecondaryActivate method. + SecondaryActivate(x int32, y int32) (err *dbus.Error) + // Scroll is org.kde.StatusNotifierItem.Scroll method. + Scroll(delta int32, orientation string) (err *dbus.Error) +} + +// ExportStatusNotifierItem exports the given object that implements org.kde.StatusNotifierItem on the bus. +func ExportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath, v StatusNotifierItemer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "ContextMenu": v.ContextMenu, + "Activate": v.Activate, + "SecondaryActivate": v.SecondaryActivate, + "Scroll": v.Scroll, + }, path, InterfaceStatusNotifierItem) +} + +// UnexportStatusNotifierItem unexports org.kde.StatusNotifierItem interface on the named path. +func UnexportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceStatusNotifierItem) +} + +// UnimplementedStatusNotifierItem can be embedded to have forward compatible server implementations. +type UnimplementedStatusNotifierItem struct{} + +func (*UnimplementedStatusNotifierItem) iface() string { + return InterfaceStatusNotifierItem +} + +func (*UnimplementedStatusNotifierItem) ContextMenu(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Activate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Scroll(delta int32, orientation string) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewStatusNotifierItem creates and allocates org.kde.StatusNotifierItem. +func NewStatusNotifierItem(object dbus.BusObject) *StatusNotifierItem { + return &StatusNotifierItem{object} +} + +// StatusNotifierItem implements org.kde.StatusNotifierItem D-Bus interface. +type StatusNotifierItem struct { + object dbus.BusObject +} + +// ContextMenu calls org.kde.StatusNotifierItem.ContextMenu method. +func (o *StatusNotifierItem) ContextMenu(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".ContextMenu", 0, x, y).Store() + return +} + +// Activate calls org.kde.StatusNotifierItem.Activate method. +func (o *StatusNotifierItem) Activate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Activate", 0, x, y).Store() + return +} + +// SecondaryActivate calls org.kde.StatusNotifierItem.SecondaryActivate method. +func (o *StatusNotifierItem) SecondaryActivate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".SecondaryActivate", 0, x, y).Store() + return +} + +// Scroll calls org.kde.StatusNotifierItem.Scroll method. +func (o *StatusNotifierItem) Scroll(ctx context.Context, delta int32, orientation string) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Scroll", 0, delta, orientation).Store() + return +} + +// GetCategory gets org.kde.StatusNotifierItem.Category property. +func (o *StatusNotifierItem) GetCategory(ctx context.Context) (category string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Category").Store(&category) + return +} + +// GetId gets org.kde.StatusNotifierItem.Id property. +func (o *StatusNotifierItem) GetId(ctx context.Context) (id string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Id").Store(&id) + return +} + +// GetTitle gets org.kde.StatusNotifierItem.Title property. +func (o *StatusNotifierItem) GetTitle(ctx context.Context) (title string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Title").Store(&title) + return +} + +// GetStatus gets org.kde.StatusNotifierItem.Status property. +func (o *StatusNotifierItem) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Status").Store(&status) + return +} + +// GetWindowId gets org.kde.StatusNotifierItem.WindowId property. +func (o *StatusNotifierItem) GetWindowId(ctx context.Context) (windowId int32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "WindowId").Store(&windowId) + return +} + +// GetIconThemePath gets org.kde.StatusNotifierItem.IconThemePath property. +func (o *StatusNotifierItem) GetIconThemePath(ctx context.Context) (iconThemePath string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconThemePath").Store(&iconThemePath) + return +} + +// GetMenu gets org.kde.StatusNotifierItem.Menu property. +func (o *StatusNotifierItem) GetMenu(ctx context.Context) (menu dbus.ObjectPath, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Menu").Store(&menu) + return +} + +// GetItemIsMenu gets org.kde.StatusNotifierItem.ItemIsMenu property. +func (o *StatusNotifierItem) GetItemIsMenu(ctx context.Context) (itemIsMenu bool, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ItemIsMenu").Store(&itemIsMenu) + return +} + +// GetIconName gets org.kde.StatusNotifierItem.IconName property. +func (o *StatusNotifierItem) GetIconName(ctx context.Context) (iconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconName").Store(&iconName) + return +} + +// GetIconPixmap gets org.kde.StatusNotifierItem.IconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetIconPixmap(ctx context.Context) (iconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconPixmap").Store(&iconPixmap) + return +} + +// GetOverlayIconName gets org.kde.StatusNotifierItem.OverlayIconName property. +func (o *StatusNotifierItem) GetOverlayIconName(ctx context.Context) (overlayIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconName").Store(&overlayIconName) + return +} + +// GetOverlayIconPixmap gets org.kde.StatusNotifierItem.OverlayIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetOverlayIconPixmap(ctx context.Context) (overlayIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconPixmap").Store(&overlayIconPixmap) + return +} + +// GetAttentionIconName gets org.kde.StatusNotifierItem.AttentionIconName property. +func (o *StatusNotifierItem) GetAttentionIconName(ctx context.Context) (attentionIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconName").Store(&attentionIconName) + return +} + +// GetAttentionIconPixmap gets org.kde.StatusNotifierItem.AttentionIconPixmap property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetAttentionIconPixmap(ctx context.Context) (attentionIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconPixmap").Store(&attentionIconPixmap) + return +} + +// GetAttentionMovieName gets org.kde.StatusNotifierItem.AttentionMovieName property. +func (o *StatusNotifierItem) GetAttentionMovieName(ctx context.Context) (attentionMovieName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionMovieName").Store(&attentionMovieName) + return +} + +// GetToolTip gets org.kde.StatusNotifierItem.ToolTip property. +// +// Annotations: +// +// @org.qtproject.QtDBus.QtTypeName = KDbusToolTipStruct +func (o *StatusNotifierItem) GetToolTip(ctx context.Context) (toolTip struct { + V0 string + V1 []struct { + V0 int32 + V1 int32 + V2 []byte + } + V2 string + V3 string +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ToolTip").Store(&toolTip) + return +} + +// StatusNotifierItem_NewTitleSignal represents org.kde.StatusNotifierItem.NewTitle signal. +type StatusNotifierItem_NewTitleSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewTitleSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewTitleSignal) Name() string { + return "NewTitle" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewTitleSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewTitleSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewTitleSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewTitleSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewTitleSignalBody is body container. +type StatusNotifierItem_NewTitleSignalBody struct { +} + +// StatusNotifierItem_NewIconSignal represents org.kde.StatusNotifierItem.NewIcon signal. +type StatusNotifierItem_NewIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconSignal) Name() string { + return "NewIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewIconSignalBody is body container. +type StatusNotifierItem_NewIconSignalBody struct { +} + +// StatusNotifierItem_NewAttentionIconSignal represents org.kde.StatusNotifierItem.NewAttentionIcon signal. +type StatusNotifierItem_NewAttentionIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewAttentionIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Name() string { + return "NewAttentionIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewAttentionIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewAttentionIconSignalBody is body container. +type StatusNotifierItem_NewAttentionIconSignalBody struct { +} + +// StatusNotifierItem_NewOverlayIconSignal represents org.kde.StatusNotifierItem.NewOverlayIcon signal. +type StatusNotifierItem_NewOverlayIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewOverlayIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Name() string { + return "NewOverlayIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewOverlayIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewOverlayIconSignalBody is body container. +type StatusNotifierItem_NewOverlayIconSignalBody struct { +} + +// StatusNotifierItem_NewStatusSignal represents org.kde.StatusNotifierItem.NewStatus signal. +type StatusNotifierItem_NewStatusSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewStatusSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewStatusSignal) Name() string { + return "NewStatus" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewStatusSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewStatusSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewStatusSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewStatusSignal) values() []interface{} { + return []interface{}{s.Body.Status} +} + +// StatusNotifierItem_NewStatusSignalBody is body container. +type StatusNotifierItem_NewStatusSignalBody struct { + Status string +} + +// StatusNotifierItem_NewIconThemePathSignal represents org.kde.StatusNotifierItem.NewIconThemePath signal. +type StatusNotifierItem_NewIconThemePathSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconThemePathSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Name() string { + return "NewIconThemePath" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconThemePathSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) values() []interface{} { + return []interface{}{s.Body.IconThemePath} +} + +// StatusNotifierItem_NewIconThemePathSignalBody is body container. +type StatusNotifierItem_NewIconThemePathSignalBody struct { + IconThemePath string +} + +// StatusNotifierItem_NewMenuSignal represents org.kde.StatusNotifierItem.NewMenu signal. +type StatusNotifierItem_NewMenuSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewMenuSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewMenuSignal) Name() string { + return "NewMenu" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewMenuSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewMenuSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewMenuSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewMenuSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewMenuSignalBody is body container. +type StatusNotifierItem_NewMenuSignalBody struct { +} diff --git a/v3/internal/debug/debug.go b/v3/internal/debug/debug.go new file mode 100644 index 000000000..394688ce7 --- /dev/null +++ b/v3/internal/debug/debug.go @@ -0,0 +1,42 @@ +package debug + +import ( + "os" + "path/filepath" + "runtime" +) + +var LocalModulePath = "" + +func init() { + // Check if .git exists in the relative directory from here: ../../.. + // If it does, we are in a local build + gitDir := RelativePath("..", "..", "..", ".git") + if _, err := os.Stat(gitDir); err == nil { + modulePath := RelativePath("..", "..", "..") + LocalModulePath, _ = filepath.Abs(modulePath) + } +} + +// RelativePath returns a qualified path created by joining the +// directory of the calling file and the given relative path. +func RelativePath(relativepath string, optionalpaths ...string) string { + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + + // If we have optional paths, join them to the relativepath + if len(optionalpaths) > 0 { + paths := []string{relativepath} + paths = append(paths, optionalpaths...) + relativepath = filepath.Join(paths...) + } + result, err := filepath.Abs(filepath.Join(localDir, relativepath)) + if err != nil { + // I'm allowing this for 1 reason only: It's fatal if the path + // supplied is wrong as it's only used internally in Wails. If we get + // that path wrong, we should know about it immediately. The other reason is + // that it cuts down a ton of unnecassary error handling. + panic(err) + } + return result +} diff --git a/v3/internal/doctor/diagnostics.go b/v3/internal/doctor/diagnostics.go new file mode 100644 index 000000000..bc6b36670 --- /dev/null +++ b/v3/internal/doctor/diagnostics.go @@ -0,0 +1,93 @@ +package doctor + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" +) + +// DiagnosticTest represents a single diagnostic test to be run +type DiagnosticTest struct { + Name string + Run func() (bool, string) // Returns success and error message if failed + HelpURL string +} + +// DiagnosticResult represents the result of a diagnostic test +type DiagnosticResult struct { + TestName string + ErrorMsg string + HelpURL string +} + +// platformDiagnostics maps platform names to their diagnostic tests +var platformDiagnostics = map[string][]DiagnosticTest{ + // Tests that run on all platforms + "all": { + { + Name: "Check Go installation", + Run: func() (bool, string) { + // This is just an example test for all platforms + if runtime.Version() == "" { + return false, "Go installation not found" + } + return true, "" + }, + HelpURL: "/getting-started/installation/", + }, + }, + // Platform specific tests + "darwin": { + { + Name: "Check for .syso file", + Run: func() (bool, string) { + // Check for .syso files in current directory + matches, err := filepath.Glob("*.syso") + if err != nil { + return false, "Error checking for .syso files" + } + if len(matches) > 0 { + return false, fmt.Sprintf("Found .syso file(s): %v. These may cause issues when building on macOS", strings.Join(matches, ", ")) + } + return true, "" + }, + HelpURL: "/troubleshooting/mac-syso", + }, + }, +} + +// RunDiagnostics executes all diagnostic tests for the current platform +func RunDiagnostics() []DiagnosticResult { + var results []DiagnosticResult + + // Run tests that apply to all platforms + if tests, exists := platformDiagnostics["all"]; exists { + for _, test := range tests { + success, errMsg := test.Run() + if !success { + results = append(results, DiagnosticResult{ + TestName: test.Name, + ErrorMsg: errMsg, + HelpURL: test.HelpURL, + }) + } + } + } + + // Run platform-specific tests + if tests, exists := platformDiagnostics[runtime.GOOS]; exists { + for _, test := range tests { + success, errMsg := test.Run() + if !success { + results = append(results, DiagnosticResult{ + TestName: test.Name, + ErrorMsg: errMsg, + HelpURL: test.HelpURL, + }) + } + } + } + + return results +} diff --git a/v3/internal/doctor/doctor.go b/v3/internal/doctor/doctor.go new file mode 100644 index 000000000..a14fcf3b3 --- /dev/null +++ b/v3/internal/doctor/doctor.go @@ -0,0 +1,274 @@ +package doctor + +import ( + "bytes" + "fmt" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "runtime/debug" + "slices" + "strconv" + "strings" + + "github.com/wailsapp/wails/v3/internal/term" + + "github.com/wailsapp/wails/v3/internal/buildinfo" + + "github.com/go-git/go-git/v5" + "github.com/jaypipes/ghw" + "github.com/pterm/pterm" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/internal/version" +) + +func Run() (err error) { + + get, err := buildinfo.Get() + if err != nil { + return err + } + _ = get + + term.Header("Wails Doctor") + + spinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone().Start("Scanning system - Please wait (this may take a long time)...") + + defer func() { + if err != nil { + spinner.Fail() + } + }() + + /** Build **/ + + // BuildSettings contains the build settings for the application + var BuildSettings map[string]string + + // BuildInfo contains the build info for the application + var BuildInfo *debug.BuildInfo + + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return fmt.Errorf("could not read build info from binary") + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + + /** Operating System **/ + + // Get system info + info, err := operatingsystem.Info() + if err != nil { + term.Error("Failed to get system information") + return err + } + + /** Wails **/ + + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := strings.TrimSpace(version.String()) + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + + platformExtras, ok := getInfo() + + dependencies := make(map[string]string) + checkPlatformDependencies(dependencies, &ok) + + spinner.Success() + + /** Output **/ + + term.Section("System") + + systemTabledata := pterm.TableData{ + {pterm.Sprint("Name"), info.Name}, + {pterm.Sprint("Version"), info.Version}, + {pterm.Sprint("ID"), info.ID}, + {pterm.Sprint("Branding"), info.Branding}, + + {pterm.Sprint("Platform"), runtime.GOOS}, + {pterm.Sprint("Architecture"), runtime.GOARCH}, + } + + mapKeys := lo.Keys(platformExtras) + slices.Sort(mapKeys) + for _, key := range mapKeys { + systemTabledata = append(systemTabledata, []string{key, platformExtras[key]}) + } + + // Probe CPU + cpus, _ := ghw.CPU() + if cpus != nil { + prefix := "CPU" + for idx, cpu := range cpus.Processors { + if len(cpus.Processors) > 1 { + prefix = "CPU " + strconv.Itoa(idx+1) + } + systemTabledata = append(systemTabledata, []string{prefix, cpu.Model}) + } + } else { + systemTabledata = append(systemTabledata, []string{"CPU", "Unknown"}) + } + + // Probe GPU + gpu, _ := ghw.GPU(ghw.WithDisableWarnings()) + if gpu != nil { + for idx, card := range gpu.GraphicsCards { + details := "Unknown" + prefix := "GPU " + strconv.Itoa(idx+1) + if card.DeviceInfo != nil { + details = fmt.Sprintf("%s (%s) - Driver: %s ", card.DeviceInfo.Product.Name, card.DeviceInfo.Vendor.Name, card.DeviceInfo.Driver) + } + systemTabledata = append(systemTabledata, []string{prefix, details}) + } + } else { + if runtime.GOOS == "darwin" { + var numCoresValue string + cmd := exec.Command("sh", "-c", "ioreg -l | grep gpu-core-count") + output, err := cmd.Output() + if err == nil { + // Look for an `=` sign, optional spaces and then an integer + re := regexp.MustCompile(`= *(\d+)`) + matches := re.FindAllStringSubmatch(string(output), -1) + numCoresValue = "Unknown" + if len(matches) > 0 { + numCoresValue = matches[0][1] + } + + } + + // Run `system_profiler SPDisplaysDataType | grep Metal` + var metalSupport string + cmd = exec.Command("sh", "-c", "system_profiler SPDisplaysDataType | grep Metal") + output, err = cmd.Output() + if err == nil { + metalSupport = ", " + strings.TrimSpace(string(output)) + } + systemTabledata = append(systemTabledata, []string{"GPU", numCoresValue + " cores" + metalSupport}) + + } else { + systemTabledata = append(systemTabledata, []string{"GPU", "Unknown"}) + } + } + + memory, _ := ghw.Memory() + var memoryText = "Unknown" + if memory != nil { + memoryText = strconv.Itoa(int(memory.TotalPhysicalBytes/1024/1024/1024)) + "GB" + } else { + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", "system_profiler SPHardwareDataType | grep 'Memory'") + output, err := cmd.Output() + if err == nil { + output = bytes.Replace(output, []byte("Memory: "), []byte(""), 1) + memoryText = strings.TrimSpace(string(output)) + } + } + } + systemTabledata = append(systemTabledata, []string{"Memory", memoryText}) + + err = pterm.DefaultTable.WithBoxed().WithData(systemTabledata).Render() + if err != nil { + return err + } + + // Build Environment + + term.Section("Build Environment") + + tableData := pterm.TableData{ + {"Wails CLI", wailsVersion}, + {"Go Version", runtime.Version()}, + } + + if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil { + buildSettingToName := map[string]string{ + "vcs.revision": "Revision", + "vcs.modified": "Modified", + } + for _, buildSetting := range buildInfo.Settings { + name := buildSettingToName[buildSetting.Key] + if name == "" { + continue + } + tableData = append(tableData, []string{name, buildSetting.Value}) + } + } + + mapKeys = lo.Keys(BuildSettings) + slices.Sort(mapKeys) + for _, key := range mapKeys { + tableData = append(tableData, []string{key, BuildSettings[key]}) + } + + err = pterm.DefaultTable.WithBoxed(true).WithData(tableData).Render() + if err != nil { + return err + } + + // Dependencies + term.Section("Dependencies") + dependenciesBox := pterm.DefaultBox.WithTitleBottomCenter().WithTitle(pterm.Gray("*") + " - Optional Dependency") + dependencyTableData := pterm.TableData{} + if len(dependencies) == 0 { + pterm.Info.Println("No dependencies found") + } else { + var optionals pterm.TableData + mapKeys = lo.Keys(dependencies) + for _, key := range mapKeys { + if strings.HasPrefix(dependencies[key], "*") { + optionals = append(optionals, []string{key, dependencies[key]}) + } else { + dependencyTableData = append(dependencyTableData, []string{key, dependencies[key]}) + } + } + dependencyTableData = append(dependencyTableData, optionals...) + dependenciesTableString, _ := pterm.DefaultTable.WithData(dependencyTableData).Srender() + dependenciesBox.Println(dependenciesTableString) + } + + // Run diagnostics after system info + term.Section("Checking for issues") + + diagnosticResults := RunDiagnostics() + if len(diagnosticResults) == 0 { + pterm.Success.Println("No issues found") + } else { + pterm.Warning.Println("Found potential issues:") + for _, result := range diagnosticResults { + pterm.Printf("โ€ข %s: %s\n", result.TestName, result.ErrorMsg) + url := result.HelpURL + if strings.HasPrefix(url, "/") { + url = "https://v3.wails.io" + url + } + pterm.Printf(" For more information: %s\n", term.Hyperlink(url, url)) + } + } + + term.Section("Diagnosis") + if !ok { + term.Warning("There are some items above that need addressing!") + } else { + term.Success("Your system is ready for Wails development!") + } + + return nil +} diff --git a/v3/internal/doctor/doctor_common.go b/v3/internal/doctor/doctor_common.go new file mode 100644 index 000000000..4befcffe8 --- /dev/null +++ b/v3/internal/doctor/doctor_common.go @@ -0,0 +1,32 @@ +package doctor + +import ( + "bytes" + "os/exec" + "strconv" + "strings" +) + +func checkCommonDependencies(result map[string]string, ok *bool) { + // Check for npm + npmVersion := []byte("Not Installed. Requires npm >= 7.0.0") + npmVersion, err := exec.Command("npm", "-v").Output() + if err != nil { + *ok = false + } else { + npmVersion = bytes.TrimSpace(npmVersion) + // Check that it's at least version 7 by converting first byte to int and checking if it's >= 7 + // Parse the semver string + semver := strings.Split(string(npmVersion), ".") + if len(semver) > 0 { + major, _ := strconv.Atoi(semver[0]) + if major < 7 { + *ok = false + npmVersion = append(npmVersion, []byte(". Installed, but requires npm >= 7.0.0")...) + } else { + *ok = true + } + } + } + result["npm"] = string(npmVersion) +} diff --git a/v3/internal/doctor/doctor_darwin.go b/v3/internal/doctor/doctor_darwin.go new file mode 100644 index 000000000..9ffb368f1 --- /dev/null +++ b/v3/internal/doctor/doctor_darwin.go @@ -0,0 +1,63 @@ +//go:build darwin + +package doctor + +import ( + "bytes" + "github.com/samber/lo" + "os/exec" + "strings" + "syscall" +) + +func getSysctl(name string) string { + value, err := syscall.Sysctl(name) + if err != nil { + return "unknown" + } + return value +} + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + ok := true + + // Determine if the app is running on Apple Silicon + // Credit: https://www.yellowduck.be/posts/detecting-apple-silicon-via-go/ + appleSilicon := "unknown" + r, err := syscall.Sysctl("sysctl.proc_translated") + if err == nil { + appleSilicon = lo.Ternary(r == "\x00\x00\x00" || r == "\x01\x00\x00", "true", "false") + } + result["Apple Silicon"] = appleSilicon + result["CPU"] = getSysctl("machdep.cpu.brand_string") + + return result, ok +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + + // Check for xcode command line tools + output, err := exec.Command("xcode-select", "-v").Output() + cliToolsVersion := "N/A. Install by running: `xcode-select --install`" + if err != nil { + *ok = false + } else { + cliToolsVersion = strings.TrimPrefix(string(output), "xcode-select version ") + cliToolsVersion = strings.TrimSpace(cliToolsVersion) + cliToolsVersion = strings.TrimSuffix(cliToolsVersion, ".") + } + result["Xcode cli tools"] = cliToolsVersion + + checkCommonDependencies(result, ok) + + // Check for nsis + nsisVersion := []byte("Not Installed. Install with `brew install makensis`.") + output, err = exec.Command("makensis", "-VERSION").Output() + if err == nil && output != nil { + nsisVersion = output + } + nsisVersion = bytes.TrimSpace(nsisVersion) + + result["*NSIS"] = string(nsisVersion) +} diff --git a/v3/internal/doctor/doctor_linux.go b/v3/internal/doctor/doctor_linux.go new file mode 100644 index 000000000..a3b0e4ac5 --- /dev/null +++ b/v3/internal/doctor/doctor_linux.go @@ -0,0 +1,42 @@ +//go:build linux + +package doctor + +import ( + "github.com/wailsapp/wails/v3/internal/doctor/packagemanager" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +func getInfo() (map[string]string, bool) { + result := make(map[string]string) + return result, true +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + info, _ := operatingsystem.Info() + + pm := packagemanager.Find(info.ID) + deps, _ := packagemanager.Dependencies(pm) + for _, dep := range deps { + var status string + + switch true { + case !dep.Installed: + if dep.Optional { + status = "[Optional] " + } else { + *ok = false + } + status += "not installed." + if dep.InstallCommand != "" { + status += " Install with: " + dep.InstallCommand + } + case dep.Version != "": + status = dep.Version + } + + result[dep.Name] = status + } + + checkCommonDependencies(result, ok) +} diff --git a/v3/internal/doctor/doctor_test.go b/v3/internal/doctor/doctor_test.go new file mode 100644 index 000000000..98f4c3f8a --- /dev/null +++ b/v3/internal/doctor/doctor_test.go @@ -0,0 +1,10 @@ +package doctor + +import "testing" + +func TestRun(t *testing.T) { + err := Run() + if err != nil { + t.Errorf("TestRun failed: %v", err) + } +} diff --git a/v3/internal/doctor/doctor_windows.go b/v3/internal/doctor/doctor_windows.go new file mode 100644 index 000000000..2ae1dd2c8 --- /dev/null +++ b/v3/internal/doctor/doctor_windows.go @@ -0,0 +1,86 @@ +//go:build windows + +package doctor + +import ( + "os/exec" + "strings" + + "github.com/samber/lo" + "github.com/wailsapp/go-webview2/webviewloader" +) + +func getInfo() (map[string]string, bool) { + ok := true + result := make(map[string]string) + result["Go WebView2Loader"] = lo.Ternary(webviewloader.UsingGoWebview2Loader, "true", "false") + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString("") + if err != nil { + ok = false + webviewVersion = "Error:" + err.Error() + } + result["WebView2 Version"] = webviewVersion + return result, ok +} + +func getNSISVersion() string { + // Execute nsis + output, err := exec.Command("makensis", "-VERSION").Output() + if err != nil { + return "Not Installed" + } + return string(output) +} + +func getMakeAppxVersion() string { + // Check if MakeAppx.exe is available (part of Windows SDK) + _, err := exec.LookPath("MakeAppx.exe") + if err != nil { + return "Not Installed" + } + return "Installed" +} + +func getMSIXPackagingToolVersion() string { + // Check if MSIX Packaging Tool is installed + // Use PowerShell to check if the app is installed from Microsoft Store + cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MSIXPackagingTool") + output, err := cmd.Output() + if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MSIXPackagingTool") { + return "Not Installed" + } + + if strings.Contains(string(output), "Version") { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "Version") { + parts := strings.Split(line, ":") + if len(parts) > 1 { + return strings.TrimSpace(parts[1]) + } + } + } + } + + return "Installed (Version Unknown)" +} + +func getSignToolVersion() string { + // Check if signtool.exe is available (part of Windows SDK) + _, err := exec.LookPath("signtool.exe") + if err != nil { + return "Not Installed" + } + return "Installed" +} + +func checkPlatformDependencies(result map[string]string, ok *bool) { + checkCommonDependencies(result, ok) + // add nsis + result["NSIS"] = getNSISVersion() + + // Add MSIX tooling checks + result["MakeAppx.exe (Windows SDK)"] = getMakeAppxVersion() + result["MSIX Packaging Tool"] = getMSIXPackagingToolVersion() + result["SignTool.exe (Windows SDK)"] = getSignToolVersion() +} diff --git a/v3/internal/doctor/packagemanager/apt.go b/v3/internal/doctor/packagemanager/apt.go new file mode 100644 index 000000000..257279081 --- /dev/null +++ b/v3/internal/doctor/packagemanager/apt.go @@ -0,0 +1,96 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +// Apt represents the Apt manager +type Apt struct { + name string + osid string +} + +// NewApt creates a new Apt instance +func NewApt(osid string) *Apt { + return &Apt{ + name: "apt", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (a *Apt) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "libgtk-3-dev", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "libwebkit2gtk-4.1-dev", SystemPackage: true, Library: true}, + {Name: "libwebkit2gtk-4.0-dev", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "build-essential", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (a *Apt) Name() string { + return a.name +} + +func (a *Apt) listPackage(name string) (string, error) { + return execCmd("apt", "list", "-qq", name) +} + +// PackageInstalled tests if the given package name is installed +func (a *Apt) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + output, err := a.listPackage(pkg.Name) + // apt list -qq returns "all" if you have packages installed globally and locally + return strings.Contains(output, "installed") || strings.Contains(output, " all"), err +} + +// PackageAvailable tests if the given package is available for installation +func (a *Apt) PackageAvailable(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + return true, nil + } + output, err := a.listPackage(pkg.Name) + // We add a space to ensure we get a full match, not partial match + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + escapechars.ReplaceAllString(output, "") + installed := strings.HasPrefix(output, pkg.Name) + a.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (a *Apt) InstallCommand(pkg *Package) string { + if !pkg.SystemPackage { + return pkg.InstallCommand + } + return "sudo apt install " + pkg.Name +} + +func (a *Apt) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 1 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/dnf.go b/v3/internal/doctor/packagemanager/dnf.go new file mode 100644 index 000000000..029a38d67 --- /dev/null +++ b/v3/internal/doctor/packagemanager/dnf.go @@ -0,0 +1,119 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "strings" +) + +// Dnf represents the Dnf manager +type Dnf struct { + name string + osid string +} + +// NewDnf creates a new Dnf instance +func NewDnf(osid string) *Dnf { + return &Dnf{ + name: "dnf", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (y *Dnf) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk4.1-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk4.0-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + {Name: "nodejs-npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (y *Dnf) Name() string { + return y.name +} + +// PackageInstalled tests if the given package name is installed +func (y *Dnf) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("dnf", "info", "installed", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, "Version") { + splitline := strings.Split(line, ":") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + + return true, err +} + +// PackageAvailable tests if the given package is available for installation +func (y *Dnf) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("dnf", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, "Version") { + splitline := strings.Split(line, ":") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (y *Dnf) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo dnf install " + pkg.Name +} + +func (y *Dnf) getPackageVersion(pkg *Package, output string) { + splitOutput := strings.Split(output, " ") + if len(splitOutput) > 0 { + pkg.Version = splitOutput[1] + } +} diff --git a/v3/internal/doctor/packagemanager/emerge.go b/v3/internal/doctor/packagemanager/emerge.go new file mode 100644 index 000000000..301955051 --- /dev/null +++ b/v3/internal/doctor/packagemanager/emerge.go @@ -0,0 +1,113 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Emerge represents the Emerge package manager +type Emerge struct { + name string + osid string +} + +// NewEmerge creates a new Emerge instance +func NewEmerge(osid string) *Emerge { + return &Emerge{ + name: "emerge", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Emerge) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "x11-libs/gtk+", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "net-libs/webkit-gtk:6", SystemPackage: true, Library: true}, + {Name: "net-libs/webkit-gtk:4", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "sys-devel/gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "dev-util/pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "net-libs/nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Emerge) Name() string { + return e.name +} + +// PackageInstalled tests if the given package name is installed +func (e *Emerge) PackageInstalled(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + regex := `.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version installed: (.*)` + installedRegex := regexp.MustCompile(regex) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + installed := false + if noOfMatches > 1 && matches[1] != "[ Not Installed ]" { + installed = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return installed, err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Emerge) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("emerge", "-s", pkg.Name+"$") + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + installedRegex := regexp.MustCompile(`.*\*\s+` + regexp.QuoteMeta(pkg.Name) + `\n(?:\S|\s)+?Latest version available: (.*)`) + matches := installedRegex.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + available := false + if noOfMatches > 1 { + available = true + pkg.Version = strings.TrimSpace(matches[1]) + } + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Emerge) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo emerge " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/eopkg.go b/v3/internal/doctor/packagemanager/eopkg.go new file mode 100644 index 000000000..060e59595 --- /dev/null +++ b/v3/internal/doctor/packagemanager/eopkg.go @@ -0,0 +1,111 @@ +//go:build linux + +package packagemanager + +import ( + "regexp" + "strings" +) + +type Eopkg struct { + name string + osid string +} + +// NewEopkg creates a new Eopkg instance +func NewEopkg(osid string) *Eopkg { + result := &Eopkg{ + name: "eopkg", + osid: osid, + } + result.intialiseName() + return result +} + +// Packages returns the packages that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (e *Eopkg) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "libgtk-3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "libwebkit-gtk-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (e *Eopkg) Name() string { + return e.name +} + +// PackageInstalled tests if the given package is installed +func (e *Eopkg) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + return strings.HasPrefix(stdout, "Installed"), err +} + +// PackageAvailable tests if the given package is available for installation +func (e *Eopkg) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("eopkg", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + output := e.removeEscapeSequences(stdout) + installed := strings.Contains(output, "Package found in Solus repository") + e.getPackageVersion(pkg, output) + return installed, err +} + +// InstallCommand returns the package manager specific command to install a package +func (e *Eopkg) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo eopkg it " + pkg.Name +} + +func (e *Eopkg) removeEscapeSequences(in string) string { + escapechars, _ := regexp.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`) + return escapechars.ReplaceAllString(in, "") +} + +func (e *Eopkg) intialiseName() { + result := "eopkg" + stdout, err := execCmd("eopkg", "--version") + if err == nil { + result = strings.TrimSpace(stdout) + } + e.name = result +} + +func (e *Eopkg) getPackageVersion(pkg *Package, output string) { + + versionRegex := regexp.MustCompile(`.*Name.*version:\s+(.*)+, release: (.*)`) + matches := versionRegex.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = matches[1] + if noOfMatches > 2 { + pkg.Version += " (r" + matches[2] + ")" + } + } +} diff --git a/v3/internal/doctor/packagemanager/nixpkgs.go b/v3/internal/doctor/packagemanager/nixpkgs.go new file mode 100644 index 000000000..4141de056 --- /dev/null +++ b/v3/internal/doctor/packagemanager/nixpkgs.go @@ -0,0 +1,151 @@ +//go:build linux + +package packagemanager + +import ( + "encoding/json" +) + +// Nixpkgs represents the Nixpkgs manager +type Nixpkgs struct { + name string + osid string +} + +type NixPackageDetail struct { + Name string + Pname string + Version string +} + +var available map[string]NixPackageDetail + +// NewNixpkgs creates a new Nixpkgs instance +func NewNixpkgs(osid string) *Nixpkgs { + available = map[string]NixPackageDetail{} + + return &Nixpkgs{ + name: "nixpkgs", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (n *Nixpkgs) Packages() Packagemap { + // Currently, only support checking the default channel. + channel := "nixpkgs" + if n.osid == "nixos" { + channel = "nixos" + } + + return Packagemap{ + "gtk3": []*Package{ + {Name: channel + ".gtk3", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: channel + ".webkitgtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: channel + ".gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: channel + ".pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: channel + ".nodejs", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (n *Nixpkgs) Name() string { + return n.name +} + +// PackageInstalled tests if the given package name is installed +func (n *Nixpkgs) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Did we get one? + installed := false + for attribute, detail := range attributes { + if attribute == pkg.Name { + installed = true + pkg.Version = detail.Version + } + break + } + + // If on NixOS, package may be installed via system config, so check the nix store. + detail, ok := available[pkg.Name] + if !installed && n.osid == "nixos" && ok { + cmd := "nix-store --query --requisites /run/current-system | cut -d- -f2- | sort | uniq | grep '^" + detail.Pname + "'" + + if pkg.Library { + cmd += " | grep 'dev$'" + } + + stdout, err = execCmd("sh", "-c", cmd) + if err != nil { + return false, nil + } + + if len(stdout) > 0 { + installed = true + } + } + + return installed, nil +} + +// PackageAvailable tests if the given package is available for installation +func (n *Nixpkgs) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + + stdout, err := execCmd("nix-env", "--json", "-qaA", pkg.Name) + if err != nil { + return false, nil + } + + var attributes map[string]NixPackageDetail + err = json.Unmarshal([]byte(stdout), &attributes) + if err != nil { + return false, err + } + + // Grab first version. + for attribute, detail := range attributes { + pkg.Version = detail.Version + available[attribute] = detail + break + } + + return len(pkg.Version) > 0, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (n *Nixpkgs) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "nix-env -iA " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/packagemanager.go b/v3/internal/doctor/packagemanager/packagemanager.go new file mode 100644 index 000000000..800b05f7a --- /dev/null +++ b/v3/internal/doctor/packagemanager/packagemanager.go @@ -0,0 +1,169 @@ +//go:build linux + +package packagemanager + +import ( + "bytes" + "os" + "os/exec" + "sort" + "strings" +) + +func execCmd(command string, args ...string) (string, error) { + cmd := exec.Command(command, args...) + var stdo, stde bytes.Buffer + cmd.Stdout = &stdo + cmd.Stderr = &stde + cmd.Env = append(os.Environ(), "LANGUAGE=en_US.utf-8") + err := cmd.Run() + return stdo.String(), err +} + +// A list of package manager commands +var pmcommands = []string{ + "eopkg", + "apt", + "dnf", + "pacman", + "emerge", + "zypper", + "nix-env", +} + +// commandExists returns true if the given command can be found on the shell +func commandExists(name string) bool { + _, err := exec.LookPath(name) + if err != nil { + return false + } + return true +} + +// Find will attempt to find the system package manager +func Find(osid string) PackageManager { + + // Loop over pmcommands + for _, pmname := range pmcommands { + if commandExists(pmname) { + return newPackageManager(pmname, osid) + } + } + return nil +} + +func newPackageManager(pmname string, osid string) PackageManager { + switch pmname { + case "eopkg": + return NewEopkg(osid) + case "apt": + return NewApt(osid) + case "dnf": + return NewDnf(osid) + case "pacman": + return NewPacman(osid) + case "emerge": + return NewEmerge(osid) + case "zypper": + return NewZypper(osid) + case "nix-env": + return NewNixpkgs(osid) + } + return nil +} + +// Dependencies scans the system for required dependencies +// Returns a list of dependencies search for, whether they were found +// and whether they were installed +func Dependencies(p PackageManager) (DependencyList, error) { + + var dependencies DependencyList + + for name, packages := range p.Packages() { + dependency := &Dependency{Name: name} + for _, pkg := range packages { + dependency.Optional = pkg.Optional + dependency.External = !pkg.SystemPackage + dependency.InstallCommand = p.InstallCommand(pkg) + packageavailable, err := p.PackageAvailable(pkg) + if err != nil { + return nil, err + } + if packageavailable { + dependency.Version = pkg.Version + dependency.PackageName = pkg.Name + installed, err := p.PackageInstalled(pkg) + if err != nil { + return nil, err + } + if installed { + dependency.Installed = true + dependency.Version = pkg.Version + if !pkg.SystemPackage { + dependency.Version = AppVersion(name) + } + } else { + dependency.InstallCommand = p.InstallCommand(pkg) + } + break + } + } + dependencies = append(dependencies, dependency) + } + + // Sort dependencies + sort.Slice(dependencies, func(i, j int) bool { + return dependencies[i].Name < dependencies[j].Name + }) + + return dependencies, nil +} + +// AppVersion returns the version for application related to the given package +func AppVersion(name string) string { + + if name == "gcc" { + return gccVersion() + } + + if name == "pkg-config" { + return pkgConfigVersion() + } + + if name == "npm" { + return npmVersion() + } + + return "" + +} + +func gccVersion() string { + + var version string + var err error + + // Try "-dumpfullversion" + version, err = execCmd("gcc", "-dumpfullversion") + if err != nil { + + // Try -dumpversion + // We ignore the error as this function is not for testing whether the + // application exists, only that we can get the version number + dumpversion, err := execCmd("gcc", "-dumpversion") + if err == nil { + version = dumpversion + } + } + return strings.TrimSpace(version) +} + +func pkgConfigVersion() string { + version, _ := execCmd("pkg-config", "--version") + return strings.TrimSpace(version) +} + +func npmVersion() string { + version, _ := execCmd("npm", "--version") + return strings.TrimSpace(version) +} diff --git a/v3/internal/doctor/packagemanager/pacman.go b/v3/internal/doctor/packagemanager/pacman.go new file mode 100644 index 000000000..48f24164c --- /dev/null +++ b/v3/internal/doctor/packagemanager/pacman.go @@ -0,0 +1,113 @@ +//go:build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Pacman represents the Pacman package manager +type Pacman struct { + name string + osid string +} + +// NewPacman creates a new Pacman instance +func NewPacman(osid string) *Pacman { + return &Pacman{ + name: "pacman", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (p *Pacman) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk-4.1", SystemPackage: true, Library: true}, + {Name: "webkit2gtk", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkgconf", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (p *Pacman) Name() string { + return p.name +} + +// PackageInstalled tests if the given package name is installed +func (p *Pacman) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("pacman", "-Q", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + splitoutput := strings.Split(stdout, "\n") + for _, line := range splitoutput { + if strings.HasPrefix(line, pkg.Name) { + splitline := strings.Split(line, " ") + pkg.Version = strings.TrimSpace(splitline[1]) + } + } + + return true, err +} + +// PackageAvailable tests if the given package is available for installation +func (p *Pacman) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + output, err := execCmd("pacman", "-Si", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + reg := regexp.MustCompile(`.*Version.*?:\s+(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } + + return true, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (p *Pacman) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo pacman -S " + pkg.Name +} diff --git a/v3/internal/doctor/packagemanager/pm.go b/v3/internal/doctor/packagemanager/pm.go new file mode 100644 index 000000000..6b6ced9c0 --- /dev/null +++ b/v3/internal/doctor/packagemanager/pm.go @@ -0,0 +1,65 @@ +//go:build linux + +package packagemanager + +// Package contains information about a system package +type Package struct { + Name string + Version string + InstallCommand string + InstallCheck func() bool + SystemPackage bool + Library bool + Optional bool +} + +type Packagemap = map[string][]*Package + +// PackageManager is a common interface across all package managers +type PackageManager interface { + Name() string + Packages() Packagemap + PackageInstalled(*Package) (bool, error) + PackageAvailable(*Package) (bool, error) + InstallCommand(*Package) string +} + +// Dependency represents a system package that we require +type Dependency struct { + Name string + PackageName string + Installed bool + InstallCommand string + Version string + Optional bool + External bool +} + +// DependencyList is a list of Dependency instances +type DependencyList []*Dependency + +// InstallAllRequiredCommand returns the command you need to use to install all required dependencies +func (d DependencyList) InstallAllRequiredCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && !dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} + +// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies +func (d DependencyList) InstallAllOptionalCommand() string { + + result := "" + for _, dependency := range d { + if !dependency.Installed && dependency.Optional { + result += " - " + dependency.Name + ": " + dependency.InstallCommand + "\n" + } + } + + return result +} diff --git a/v3/internal/doctor/packagemanager/zypper.go b/v3/internal/doctor/packagemanager/zypper.go new file mode 100644 index 000000000..7f24b0aca --- /dev/null +++ b/v3/internal/doctor/packagemanager/zypper.go @@ -0,0 +1,122 @@ +//go:build linux +// +build linux + +package packagemanager + +import ( + "os/exec" + "regexp" + "strings" +) + +// Zypper represents the Zypper package manager +type Zypper struct { + name string + osid string +} + +// NewZypper creates a new Zypper instance +func NewZypper(osid string) *Zypper { + return &Zypper{ + name: "zypper", + osid: osid, + } +} + +// Packages returns the libraries that we need for Wails to compile +// They will potentially differ on different distributions or versions +func (z *Zypper) Packages() Packagemap { + return Packagemap{ + "gtk3": []*Package{ + {Name: "gtk3-devel", SystemPackage: true, Library: true}, + }, + "webkit2gtk": []*Package{ + {Name: "webkit2gtk4_1-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-soup2-devel", SystemPackage: true, Library: true}, + {Name: "webkit2gtk3-devel", SystemPackage: true, Library: true}, + }, + "gcc": []*Package{ + {Name: "gcc-c++", SystemPackage: true}, + }, + "pkg-config": []*Package{ + {Name: "pkg-config", SystemPackage: true}, + {Name: "pkgconf-pkg-config", SystemPackage: true}, + }, + "npm": []*Package{ + {Name: "npm10", SystemPackage: true}, + }, + } +} + +// Name returns the name of the package manager +func (z *Zypper) Name() string { + return z.name +} + +// PackageInstalled tests if the given package name is installed +func (z *Zypper) PackageInstalled(pkg *Package) (bool, error) { + if !pkg.SystemPackage { + if pkg.InstallCheck != nil { + return pkg.InstallCheck(), nil + } + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + reg := regexp.MustCompile(`.*Installed\s*:\s*(Yes)\s*`) + matches := reg.FindStringSubmatch(stdout) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + z.getPackageVersion(pkg, stdout) + } + return noOfMatches > 1, err +} + +// PackageAvailable tests if the given package is available for installation +func (z *Zypper) PackageAvailable(pkg *Package) (bool, error) { + if pkg.SystemPackage == false { + return false, nil + } + stdout, err := execCmd("zypper", "info", pkg.Name) + // We add a space to ensure we get a full match, not partial match + if err != nil { + _, ok := err.(*exec.ExitError) + if ok { + return false, nil + } + return false, err + } + + available := strings.Contains(stdout, "Information for package") + if available { + z.getPackageVersion(pkg, stdout) + } + + return available, nil +} + +// InstallCommand returns the package manager specific command to install a package +func (z *Zypper) InstallCommand(pkg *Package) string { + if pkg.SystemPackage == false { + return pkg.InstallCommand + } + return "sudo zypper in " + pkg.Name +} + +func (z *Zypper) getPackageVersion(pkg *Package, output string) { + + reg := regexp.MustCompile(`.*Version.*:(.*)`) + matches := reg.FindStringSubmatch(output) + pkg.Version = "" + noOfMatches := len(matches) + if noOfMatches > 1 { + pkg.Version = strings.TrimSpace(matches[1]) + } +} diff --git a/v3/internal/fileexplorer/fileexplorer.go b/v3/internal/fileexplorer/fileexplorer.go new file mode 100644 index 000000000..bc0048f94 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer.go @@ -0,0 +1,61 @@ +package fileexplorer + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "time" +) + +func OpenFileManager(path string, selectFile bool) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + path = os.ExpandEnv(path) + path = filepath.Clean(path) + absPath, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("failed to resolve the absolute path: %w", err) + } + path = absPath + if pathInfo, err := os.Stat(path); err != nil { + return fmt.Errorf("failed to access the specified path: %w", err) + } else { + selectFile = selectFile && !pathInfo.IsDir() + } + + var ( + ignoreExitCode bool = false + ) + + switch runtime.GOOS { + case "windows": + // NOTE: Disabling the exit code check on Windows system. Workaround for explorer.exe + // exit code handling (https://github.com/microsoft/WSL/issues/6565) + ignoreExitCode = true + case "darwin", "linux": + default: + return errors.New("unsupported platform: " + runtime.GOOS) + } + + explorerBin, explorerArgs, err := explorerBinArgs(path, selectFile) + if err != nil { + return fmt.Errorf("failed to determine the file explorer binary: %w", err) + } + + cmd := exec.CommandContext(ctx, explorerBin, explorerArgs...) + cmd.SysProcAttr = sysProcAttr(path, selectFile) + cmd.Stdout = nil + cmd.Stderr = nil + + if err := cmd.Run(); err != nil { + if !ignoreExitCode { + return fmt.Errorf("failed to open the file explorer: %w", err) + } + } + return nil +} diff --git a/v3/internal/fileexplorer/fileexplorer_darwin.go b/v3/internal/fileexplorer/fileexplorer_darwin.go new file mode 100644 index 000000000..ce16d1206 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_darwin.go @@ -0,0 +1,19 @@ +//go:build darwin + +package fileexplorer + +import "syscall" + +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + args := []string{} + if selectFile { + args = append(args, "-R") + } + + args = append(args, path) + return "open", args, nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} diff --git a/v3/internal/fileexplorer/fileexplorer_linux.go b/v3/internal/fileexplorer/fileexplorer_linux.go new file mode 100644 index 000000000..bf0186470 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_linux.go @@ -0,0 +1,97 @@ +//go:build linux + +package fileexplorer + +import ( + "bytes" + "fmt" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + ini "gopkg.in/ini.v1" +) + +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + // Map of field codes to their replacements + var fieldCodes = map[string]string{ + "%d": "", + "%D": "", + "%n": "", + "%N": "", + "%v": "", + "%m": "", + "%f": path, + "%F": path, + "%u": pathToURI(path), + "%U": pathToURI(path), + } + fileManagerQuery := exec.Command("xdg-mime", "query", "default", "inode/directory") + buf := new(bytes.Buffer) + fileManagerQuery.Stdout = buf + fileManagerQuery.Stderr = nil + + if err := fileManagerQuery.Run(); err != nil { + return fallbackExplorerBinArgs(path, selectFile) + } + + desktopFile, err := findDesktopFile(strings.TrimSpace((buf.String()))) + if err != nil { + return fallbackExplorerBinArgs(path, selectFile) + } + + cfg, err := ini.Load(desktopFile) + if err != nil { + // Opting to fallback rather than fail + return fallbackExplorerBinArgs(path, selectFile) + } + + exec := cfg.Section("Desktop Entry").Key("Exec").String() + for fieldCode, replacement := range fieldCodes { + exec = strings.ReplaceAll(exec, fieldCode, replacement) + } + args := strings.Fields(exec) + if !strings.Contains(strings.Join(args, " "), path) { + args = append(args, path) + } + + return args[0], args[1:], nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} + +func fallbackExplorerBinArgs(path string, selectFile bool) (string, []string, error) { + // NOTE: The linux fallback explorer opening is not supporting file selection + path = filepath.Dir(path) + return "xdg-open", []string{path}, nil +} + +func pathToURI(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + return path + } + return "file://" + url.PathEscape(absPath) +} + +func findDesktopFile(xdgFileName string) (string, error) { + paths := []string{ + filepath.Join(os.Getenv("XDG_DATA_HOME"), "applications"), + filepath.Join(os.Getenv("HOME"), ".local", "share", "applications"), + "/usr/share/applications", + } + + for _, path := range paths { + desktopFile := filepath.Join(path, xdgFileName) + if _, err := os.Stat(desktopFile); err == nil { + return desktopFile, nil + } + } + err := fmt.Errorf("desktop file not found: %s", xdgFileName) + return "", err +} diff --git a/v3/internal/fileexplorer/fileexplorer_test.go b/v3/internal/fileexplorer/fileexplorer_test.go new file mode 100644 index 000000000..8f7a9b39f --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_test.go @@ -0,0 +1,83 @@ +package fileexplorer_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/wailsapp/wails/v3/internal/fileexplorer" +) + +// Credit: https://stackoverflow.com/a/50631395 +func skipCI(t *testing.T) { + if os.Getenv("CI") != "" { + t.Skip("Skipping testing in CI environment") + } +} + +func TestFileExplorer(t *testing.T) { + skipCI(t) + // TestFileExplorer verifies that the OpenFileManager function correctly handles: + // - Opening files in the native file manager across different platforms + // - Selecting files when the selectFile parameter is true + // - Various error conditions like non-existent paths + tempDir := t.TempDir() // Create a temporary directory for tests + + tests := []struct { + name string + path string + selectFile bool + expectedErr error + }{ + {"Open Existing File", tempDir, false, nil}, + {"Select Existing File", tempDir, true, nil}, + {"Non-Existent Path", "/path/does/not/exist", false, fmt.Errorf("failed to access the specified path: /path/does/not/exist")}, + {"Path with Special Characters", filepath.Join(tempDir, "test space.txt"), true, nil}, + {"No Permission Path", "/root/test.txt", false, fmt.Errorf("failed to open the file explorer: /root/test.txt")}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Run("Windows", func(t *testing.T) { + runPlatformTest(t, "windows") + }) + t.Run("Linux", func(t *testing.T) { + runPlatformTest(t, "linux") + }) + t.Run("Darwin", func(t *testing.T) { + runPlatformTest(t, "darwin") + }) + }) + } +} + +func runPlatformTest(t *testing.T, platform string) { + if runtime.GOOS != platform { + t.Skipf("Skipping test on non-%s platform", strings.ToTitle(platform)) + } + + testFile := filepath.Join(t.TempDir(), "test.txt") + if err := os.WriteFile(testFile, []byte("Test file contents"), 0644); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + selectFile bool + }{ + {"OpenFile", false}, + {"SelectFile", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := fileexplorer.OpenFileManager(testFile, test.selectFile) + if err != nil { + t.Errorf("OpenFileManager(%s, %v) error = %v", testFile, test.selectFile, err) + } + }) + } +} diff --git a/v3/internal/fileexplorer/fileexplorer_windows.go b/v3/internal/fileexplorer/fileexplorer_windows.go new file mode 100644 index 000000000..6498c5f26 --- /dev/null +++ b/v3/internal/fileexplorer/fileexplorer_windows.go @@ -0,0 +1,24 @@ +//go:build windows + +package fileexplorer + +import ( + "fmt" + "syscall" +) + +func explorerBinArgs(path string, selectFile bool) (string, []string, error) { + return "explorer.exe", []string{}, nil +} + +func sysProcAttr(path string, selectFile bool) *syscall.SysProcAttr { + if selectFile { + return &syscall.SysProcAttr{ + CmdLine: fmt.Sprintf("explorer.exe /select,\"%s\"", path), + } + } else { + return &syscall.SysProcAttr{ + CmdLine: fmt.Sprintf("explorer.exe \"%s\"", path), + } + } +} diff --git a/v3/internal/flags/bindings.go b/v3/internal/flags/bindings.go new file mode 100644 index 000000000..acef15006 --- /dev/null +++ b/v3/internal/flags/bindings.go @@ -0,0 +1,96 @@ +package flags + +import ( + "errors" + "slices" + "strings" + "unicode/utf8" +) + +type GenerateBindingsOptions struct { + BuildFlagsString string `name:"f" description:"A list of additional space-separated Go build flags. Flags (or parts of them) can be wrapped in single or double quotes to include spaces"` + OutputDirectory string `name:"d" description:"The output directory" default:"frontend/bindings"` + ModelsFilename string `name:"models" description:"File name for exported JS/TS models (excluding the extension)" default:"models"` + IndexFilename string `name:"index" description:"File name for JS/TS package indexes (excluding the extension)" default:"index"` + TS bool `name:"ts" description:"Generate Typescript bindings"` + UseInterfaces bool `name:"i" description:"Generate Typescript interfaces instead of classes"` + UseBundledRuntime bool `name:"b" description:"Use the bundled runtime instead of importing the npm package"` + UseNames bool `name:"names" description:"Use names instead of IDs for the binding calls"` + NoIndex bool `name:"noindex" description:"Do not generate JS/TS index files"` + DryRun bool `name:"dry" description:"Do not write output files"` + Silent bool `name:"silent" description:"Silent mode"` + Verbose bool `name:"v" description:"Enable debug output"` + Clean bool `name:"clean" description:"Clean output directory before generation" default:"true"` +} + +var ErrUnmatchedQuote = errors.New("build flags contain an unmatched quote") + +func isWhitespace(r rune) bool { + // We use Go's definition of whitespace instead of the Unicode ones + return r == ' ' || r == '\t' || r == '\r' || r == '\n' +} + +func isNonWhitespace(r rune) bool { + return !isWhitespace(r) +} + +func isQuote(r rune) bool { + return r == '\'' || r == '"' +} + +func isQuoteOrWhitespace(r rune) bool { + return isQuote(r) || isWhitespace(r) +} + +func (options *GenerateBindingsOptions) BuildFlags() (flags []string, err error) { + str := options.BuildFlagsString + + // temporary buffer for flag assembly + flag := make([]byte, 0, 32) + + for start := strings.IndexFunc(str, isNonWhitespace); start >= 0; start = strings.IndexFunc(str, isNonWhitespace) { + // each iteration starts at the beginning of a flag + // skip initial whitespace + str = str[start:] + + // iterate over all quoted and unquoted parts of the flag and join them + for { + breakpoint := strings.IndexFunc(str, isQuoteOrWhitespace) + if breakpoint < 0 { + breakpoint = len(str) + } + + // append everything up to the breakpoint + flag = append(flag, str[:breakpoint]...) + str = str[breakpoint:] + + quote, quoteSize := utf8.DecodeRuneInString(str) + if !isQuote(quote) { + // if the breakpoint is not a quote, we reached the end of the flag + break + } + + // otherwise, look for the closing quote + str = str[quoteSize:] + closingQuote := strings.IndexRune(str, quote) + + // closing quote not found, append everything to the last flag and raise an error + if closingQuote < 0 { + flag = append(flag, str...) + str = "" + err = ErrUnmatchedQuote + break + } + + // closing quote found, append quoted content to the flag and restart after the quote + flag = append(flag, str[:closingQuote]...) + str = str[closingQuote+quoteSize:] + } + + // append a clone of the flag to the result, then reuse buffer space + flags = append(flags, string(slices.Clone(flag))) + flag = flag[:0] + } + + return +} diff --git a/v3/internal/flags/bindings_test.go b/v3/internal/flags/bindings_test.go new file mode 100644 index 000000000..506ca1b00 --- /dev/null +++ b/v3/internal/flags/bindings_test.go @@ -0,0 +1,125 @@ +package flags + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func TestBuildFlags(t *testing.T) { + tests := []struct { + name string + input string + wantFlags []string + wantErr bool + }{ + { + name: "empty string", + input: "", + wantFlags: nil, + }, + { + name: "single flag, multiple spaces", + input: " -v ", + wantFlags: []string{"-v"}, + }, + { + name: "multiple flags, complex spaces", + input: " \t-v\r\n-x", + wantFlags: []string{"-v", "-x"}, + }, + { + name: "empty flag (single quotes)", + input: `''`, + wantFlags: []string{""}, + }, + { + name: "empty flag (double quotes)", + input: `""`, + wantFlags: []string{""}, + }, + { + name: "flag with spaces (single quotes)", + input: `'a b'`, + wantFlags: []string{"a \tb"}, + }, + { + name: "flag with spaces (double quotes)", + input: `'a b'`, + wantFlags: []string{"a \tb"}, + }, + { + name: "mixed quoted and non-quoted flags (single quotes)", + input: `-v 'a b ' -x`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "mixed quoted and non-quoted flags (double quotes)", + input: `-v "a b " -x`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "mixed quoted and non-quoted flags (mixed quotes)", + input: `-v "a b " '-x'`, + wantFlags: []string{"-v", "a b ", "-x"}, + }, + { + name: "double quote within single quotes", + input: `' " '`, + wantFlags: []string{" \" "}, + }, + { + name: "single quote within double quotes", + input: `" ' "`, + wantFlags: []string{" ' "}, + }, + { + name: "unmatched single quote", + input: `-v "a b " '-x -y`, + wantFlags: []string{"-v", "a b ", "-x -y"}, + wantErr: true, + }, + { + name: "unmatched double quote", + input: `-v "a b " "-x -y`, + wantFlags: []string{"-v", "a b ", "-x -y"}, + wantErr: true, + }, + { + name: "mismatched single quote", + input: `-v "a b " '-x" -y`, + wantFlags: []string{"-v", "a b ", "-x\" -y"}, + wantErr: true, + }, + { + name: "mismatched double quote", + input: `-v "a b " "-x' -y`, + wantFlags: []string{"-v", "a b ", "-x' -y"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := GenerateBindingsOptions{ + BuildFlagsString: tt.input, + } + + var wantErr error = nil + if tt.wantErr { + wantErr = ErrUnmatchedQuote + } + + gotFlags, gotErr := options.BuildFlags() + + if diff := cmp.Diff(tt.wantFlags, gotFlags); diff != "" { + t.Errorf("BuildFlags() unexpected result: %s\n", diff) + } + + if diff := cmp.Diff(wantErr, gotErr, cmpopts.EquateErrors()); diff != "" { + t.Errorf("BuildFlags() unexpected error: %s\n", diff) + } + }) + } +} diff --git a/v3/internal/flags/common.go b/v3/internal/flags/common.go new file mode 100644 index 000000000..e58eff411 --- /dev/null +++ b/v3/internal/flags/common.go @@ -0,0 +1,5 @@ +package flags + +type Common struct { + NoColour bool `description:"Disable colour in output"` +} diff --git a/v3/internal/flags/init.go b/v3/internal/flags/init.go new file mode 100644 index 000000000..1087d5c6f --- /dev/null +++ b/v3/internal/flags/init.go @@ -0,0 +1,21 @@ +package flags + +type Init struct { + Common + + PackageName string `name:"p" description:"Package name" default:"main"` + TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url" default:"vanilla"` + ProjectName string `name:"n" description:"Name of project" default:""` + ProjectDir string `name:"d" description:"Project directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` + List bool `name:"l" description:"List templates"` + Git string `name:"git" description:"Git repository URL to initialize (e.g. github.com/username/project)"` + ProductName string `description:"The name of the product" default:"My Product"` + ProductDescription string `description:"The description of the product" default:"My Product Description"` + ProductVersion string `description:"The version of the product" default:"0.1.0"` + ProductCompany string `description:"The company of the product" default:"My Company"` + ProductCopyright string `description:"The copyright notice" default:"\u00a9 now, My Company"` + ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"` + ProductIdentifier string `description:"The product identifier, e.g com.mycompany.myproduct"` + SkipWarning bool `name:"s" description:"Skips the warning message when using remote templates"` +} diff --git a/v3/internal/flags/msix.go b/v3/internal/flags/msix.go new file mode 100644 index 000000000..17bbbd446 --- /dev/null +++ b/v3/internal/flags/msix.go @@ -0,0 +1,26 @@ +package flags + +// ToolMSIX represents the options for the MSIX packaging command +type ToolMSIX struct { + Common + + // Project configuration + ConfigPath string `name:"config" description:"Path to the project configuration file" default:"wails.json"` + + // MSIX package information + Publisher string `name:"publisher" description:"Publisher name for the MSIX package (e.g., CN=CompanyName)" default:""` + + // Certificate for signing + CertificatePath string `name:"cert" description:"Path to the certificate file for signing the MSIX package" default:""` + CertificatePassword string `name:"cert-password" description:"Password for the certificate file" default:""` + + // Build options + Arch string `name:"arch" description:"Architecture of the package (x64, x86, arm64)" default:"x64"` + ExecutableName string `name:"name" description:"Name of the executable in the package" default:""` + ExecutablePath string `name:"executable" description:"Path to the executable file to package" default:""` + OutputPath string `name:"out" description:"Path where the MSIX package will be saved" default:""` + + // Tool selection + UseMsixPackagingTool bool `name:"use-msix-tool" description:"Use the Microsoft MSIX Packaging Tool for packaging" default:"false"` + UseMakeAppx bool `name:"use-makeappx" description:"Use MakeAppx.exe for packaging" default:"true"` +} diff --git a/v3/internal/flags/package.go b/v3/internal/flags/package.go new file mode 100644 index 000000000..bd56107c5 --- /dev/null +++ b/v3/internal/flags/package.go @@ -0,0 +1,13 @@ +package flags + +// ToolPackage represents the options for the package command +type ToolPackage struct { + Common + + Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux, dmg)" default:"deb"` + ExecutableName string `name:"name" description:"Name of the executable to package" default:"myapp"` + ConfigPath string `name:"config" description:"Path to the package configuration file" default:""` + Out string `name:"out" description:"Path to the output dir" default:"."` + BackgroundImage string `name:"background" description:"Path to an optional background image for the DMG" default:""` + CreateDMG bool `name:"create-dmg" description:"Create a DMG file (macOS only)" default:"false"` +} diff --git a/v3/internal/flags/service.go b/v3/internal/flags/service.go new file mode 100644 index 000000000..f7fd8ec2e --- /dev/null +++ b/v3/internal/flags/service.go @@ -0,0 +1,14 @@ +package flags + +type ServiceInit struct { + Name string `name:"n" description:"Name of service" default:"example_service"` + Description string `name:"d" description:"Description of service" default:"Example service"` + PackageName string `name:"p" description:"Package name for service" default:""` + OutputDir string `name:"o" description:"Output directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` + Author string `name:"a" description:"Author of service" default:""` + Version string `name:"v" description:"Version of service" default:""` + Website string `name:"w" description:"Website of service" default:""` + Repository string `name:"r" description:"Repository of service" default:""` + License string `name:"l" description:"License of service" default:""` +} diff --git a/v3/internal/flags/task_wrapper.go b/v3/internal/flags/task_wrapper.go new file mode 100644 index 000000000..4a6ade362 --- /dev/null +++ b/v3/internal/flags/task_wrapper.go @@ -0,0 +1,13 @@ +package flags + +type Build struct { + Common +} + +type Dev struct { + Common +} + +type Package struct { + Common +} diff --git a/v3/internal/generator/.gitignore b/v3/internal/generator/.gitignore new file mode 100644 index 000000000..3ed83544e --- /dev/null +++ b/v3/internal/generator/.gitignore @@ -0,0 +1,4 @@ +.task +node_modules +testdata/output/**/*.got.[jt]s +testdata/output/**/*.got.log diff --git a/v3/internal/generator/README.md b/v3/internal/generator/README.md new file mode 100644 index 000000000..20b879245 --- /dev/null +++ b/v3/internal/generator/README.md @@ -0,0 +1,6 @@ +# Generator + +This package contains the static analyser used for parsing Wails projects so that we may: + +- Generate the bindings for the frontend +- Generate Typescript definitions for the structs used by the bindings diff --git a/v3/internal/generator/Taskfile.yaml b/v3/internal/generator/Taskfile.yaml new file mode 100644 index 000000000..491e426b9 --- /dev/null +++ b/v3/internal/generator/Taskfile.yaml @@ -0,0 +1,56 @@ +# https://taskfile.dev + +version: "3" + +shopt: [globstar] + +tasks: + clean: + cmds: + - rm -rf ./testdata/output/**/*.got.[jt]s ./testdata/output/**/*.got.log + + test: + cmds: + - go test -count=1 -v . + - task: test:check + + test:analyse: + cmds: + - go test -count=1 -v -run ^TestAnalyser . + + test:constants: + cmds: + - go test -v -count=1 -run ^TestGenerateConstants . + + test:generate: + cmds: + - go test -v -count=1 -run ^TestGenerator . + - task: test:check + + test:regenerate: + cmds: + - cmd: rm -rf ./testdata/output/* + - cmd: go test -v -count=1 -run ^TestGenerator . + ignore_error: true + - task: test:generate + + test:check: + dir: ./testdata + deps: + - install-deps + cmds: + - npx tsc + - npx madge --circular output/ + + install-deps: + internal: true + dir: ./testdata + sources: + - package.json + cmds: + - npm install + + update: + dir: ./testdata + cmds: + - npx npm-check-updates -u diff --git a/v3/internal/generator/analyse.go b/v3/internal/generator/analyse.go new file mode 100644 index 000000000..411e6ff4f --- /dev/null +++ b/v3/internal/generator/analyse.go @@ -0,0 +1,202 @@ +package generator + +import ( + "fmt" + "go/token" + "go/types" + "iter" + + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// FindServices scans the given packages for invocations +// of the NewService function from the Wails application package. +// +// Whenever one is found and the type of its unique argument +// is a valid service type, the corresponding named type object +// is passed to yield. +// +// Results are deduplicated, i.e. yield is called at most once per object. +// +// If yield returns false, FindBoundTypes returns immediately. +func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger) (iter.Seq[*types.TypeName], error) { + type instanceInfo struct { + args *types.TypeList + pos token.Position + } + + type target struct { + obj types.Object + param int + } + + type targetInfo struct { + target + cause token.Position + } + + // instances maps objects (TypeName or Func) to their instance list. + instances := make(map[types.Object][]instanceInfo) + + // owner maps type parameter objects to their parent object (TypeName or Func) + owner := make(map[*types.TypeName]types.Object) + + // scheduled holds the set of type parameters + // that have been already scheduled for analysis, + // for deduplication. + scheduled := make(map[target]bool) + + // next lists type parameter objects that have yet to be analysed. + var next []targetInfo + + // Initialise instance/owner maps and detect application.NewService. + for _, pkg := range pkgs { + for ident, instance := range pkg.TypesInfo.Instances { + obj := pkg.TypesInfo.Uses[ident] + + // Add to instance map. + objInstances, seen := instances[obj] + instances[obj] = append(objInstances, instanceInfo{ + instance.TypeArgs, + pkg.Fset.Position(ident.Pos()), + }) + + if seen { + continue + } + + // Object seen for the first time: + // add type params to owner map. + var tp *types.TypeParamList + + if t, ok := obj.Type().(interface{ TypeParams() *types.TypeParamList }); ok { + tp = t.TypeParams() + } else { + // Instantiated object has unexpected kind: + // the spec might have changed. + logger.Warningf( + "unexpected instantiation for %s: please report this to Wails maintainers", + types.ObjectString(obj, nil), + ) + continue + } + + // Add type params to owner map. + for i := range tp.Len() { + if param := tp.At(i).Obj(); param != nil { + owner[param] = obj + } + } + + // If this is a named type, process methods. + if recv, ok := obj.Type().(*types.Named); ok && recv.NumMethods() > 0 { + // Register receiver type params. + for i := range recv.NumMethods() { + tp := recv.Method(i).Type().(*types.Signature).RecvTypeParams() + for j := range tp.Len() { + if param := tp.At(j).Obj(); param != nil { + owner[param] = obj + } + } + } + } + + if len(next) > 0 { + // application.NewService has been found already. + continue + } + + fn, ok := obj.(*types.Func) + if !ok { + continue + } + + // Detect application.NewService + if fn.Name() == "NewService" && fn.Pkg().Path() == systemPaths.ApplicationPackage { + // Check signature. + signature := fn.Type().(*types.Signature) + if signature.Params().Len() > 2 || signature.Results().Len() != 1 || tp.Len() != 1 || tp.At(0).Obj() == nil { + logger.Warningf("Param Len: %d, Results Len: %d, tp.Len: %d, tp.At(0).Obj(): %v", signature.Params().Len(), signature.Results().Len(), tp.Len(), tp.At(0).Obj()) + return nil, ErrBadApplicationPackage + } + + // Schedule unique type param for analysis. + tgt := target{obj, 0} + scheduled[tgt] = true + next = append(next, targetInfo{target: tgt}) + } + } + } + + // found tracks service types that have been found so far, for deduplication. + found := make(map[*types.TypeName]bool) + + return func(yield func(*types.TypeName) bool) { + // Process targets. + for len(next) > 0 { + // Pop one target off the next list. + tgt := next[len(next)-1] + next = next[:len(next)-1] + + // Prepare indirect binding message. + indirectMsg := "" + if tgt.cause.IsValid() { + indirectMsg = fmt.Sprintf(" (indirectly bound at %s)", tgt.cause) + } + + for _, instance := range instances[tgt.obj] { + // Retrieve type argument. + serviceType := types.Unalias(instance.args.At(tgt.param)) + + var named *types.Named + + switch t := serviceType.(type) { + case *types.Named: + // Process named type. + named = t.Origin() + + case *types.TypeParam: + // Schedule type parameter for analysis. + newtgt := target{owner[t.Obj()], t.Index()} + if !scheduled[newtgt] { + scheduled[newtgt] = true + + // Retrieve position of call to application.NewService + // that caused this target to be scheduled. + cause := tgt.cause + if !tgt.cause.IsValid() { + // This _is_ a call to application.NewService. + cause = instance.pos + } + + // Push on next list. + next = append(next, targetInfo{newtgt, cause}) + } + continue + + default: + logger.Warningf("%s: ignoring anonymous service type %s%s", instance.pos, serviceType, indirectMsg) + continue + } + + // Reject interfaces and generic types. + if types.IsInterface(named.Underlying()) { + logger.Warningf("%s: ignoring interface service type %s%s", instance.pos, named, indirectMsg) + continue + } else if named.TypeParams() != nil { + logger.Warningf("%s: ignoring generic service type %s", instance.pos, named, indirectMsg) + continue + } + + // Record and yield type object. + if !found[named.Obj()] { + found[named.Obj()] = true + if !yield(named.Obj()) { + return + } + } + } + } + }, nil +} diff --git a/v3/internal/generator/analyse_test.go b/v3/internal/generator/analyse_test.go new file mode 100644 index 000000000..576e56f6f --- /dev/null +++ b/v3/internal/generator/analyse_test.go @@ -0,0 +1,116 @@ +package generator + +import ( + "encoding/json" + "errors" + "go/types" + "os" + "path" + "path/filepath" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/generator/config" +) + +func TestAnalyser(t *testing.T) { + type testParams struct { + name string + want []string + } + + // Gather tests from cases directory. + entries, err := os.ReadDir("testcases") + if err != nil { + t.Fatal(err) + } + + tests := make([]testParams, 0, len(entries)+1) + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + test := testParams{ + name: entry.Name(), + want: make([]string, 0), + } + + want, err := os.Open(filepath.Join("testcases", entry.Name(), "bound_types.json")) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + t.Fatal(err) + } + } else { + err = json.NewDecoder(want).Decode(&test.want) + want.Close() + if err != nil { + t.Fatal(err) + } + } + + for i := range test.want { + test.want[i] = path.Clean("github.com/wailsapp/wails/v3/internal/generator/testcases/" + test.name + test.want[i]) + } + slices.Sort(test.want) + + tests = append(tests, test) + } + + // Add global test. + { + all := testParams{ + name: "...", + } + + for _, test := range tests { + all.want = append(all.want, test.want...) + } + slices.Sort(all.want) + + tests = append(tests, all) + } + + // Resolve system package paths. + systemPaths, err := ResolveSystemPaths(nil) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + pkgPattern := "github.com/wailsapp/wails/v3/internal/generator/testcases/" + test.name + + t.Run("pkg="+test.name, func(t *testing.T) { + pkgs, err := LoadPackages(nil, pkgPattern) + if err != nil { + t.Fatal(err) + } + + for _, pkg := range pkgs { + for _, err := range pkg.Errors { + pterm.Warning.Println(err) + } + } + + got := make([]string, 0) + + services, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil)) + if err != nil { + t.Error(err) + } + + for obj := range services { + got = append(got, types.TypeString(obj.Type(), nil)) + } + + slices.Sort(got) + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Found services mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go b/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go new file mode 100644 index 000000000..bc2c99524 --- /dev/null +++ b/v3/internal/generator/collect/_reference/json_marshaler_behaviour.go @@ -0,0 +1,166 @@ +// This example explores exhaustively the behaviour of encoding/json +// when handling types that implement marshaler interfaces. +// +// When encoding values, encoding/json makes no distinction +// between pointer and base types: +// if the base type implements a marshaler interface, +// be it with plain receiver or pointer receiver, +// json.Marshal picks it up. +// +// json.Marshaler is always preferred to encoding.TextMarshaler, +// without any consideration for the receiver type. +// +// When encoding map keys, on the other hand, +// the map key type must implement encoding.TextMarshaler strictly, +// i.e. if the interface is implemented only for plain receivers, +// pointer keys will not be accepted. +// +// json.Marshaler is ignored in this case, i.e. it makes no difference +// whether the key type implements it or not. +// +// Decoding behaviour w.r.t. unmarshaler types mirrors encoding behaviour. +package main + +import ( + "encoding/json" + "fmt" +) + +type A struct{} +type B struct{} +type C struct{} +type D struct{} +type E struct{} +type F struct{} +type G struct{} +type H struct{} + +type T struct { + A A + Ap *A + B B + Bp *B + C C + Cp *C + D D + Dp *D + E E + Ep *E + F F + Fp *F + G G + Gp *G + H H + Hp *H +} + +type MT struct { + //A map[A]bool // error + //Ap map[*A]bool // error + //B map[B]bool // error + //Bp map[*B]bool // error + C map[C]bool + Cp map[*C]bool + //D map[D]bool // error + Dp map[*D]bool + E map[E]bool + Ep map[*E]bool + //F map[F]bool // error + Fp map[*F]bool + G map[G]bool + Gp map[*G]bool + //H map[H]bool // error + Hp map[*H]bool +} + +func (A) MarshalJSON() ([]byte, error) { + return []byte(`"This is j A"`), nil +} + +func (*B) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *B"`), nil +} + +func (C) MarshalText() ([]byte, error) { + return []byte(`This is t C`), nil +} + +func (*D) MarshalText() ([]byte, error) { + return []byte(`This is t *D`), nil +} + +func (E) MarshalJSON() ([]byte, error) { + return []byte(`"This is j E"`), nil +} + +func (E) MarshalText() ([]byte, error) { + return []byte(`This is t E`), nil +} + +func (F) MarshalJSON() ([]byte, error) { + return []byte(`"This is j F"`), nil +} + +func (*F) MarshalText() ([]byte, error) { + return []byte(`This is t *F`), nil +} + +func (*G) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *G"`), nil +} + +func (G) MarshalText() ([]byte, error) { + return []byte(`This is t G`), nil +} + +func (*H) MarshalJSON() ([]byte, error) { + return []byte(`"This is j *H"`), nil +} + +func (*H) MarshalText() ([]byte, error) { + return []byte(`This is t *H`), nil +} + +func main() { + t := &T{ + A{}, + &A{}, + B{}, + &B{}, + C{}, + &C{}, + D{}, + &D{}, + E{}, + &E{}, + F{}, + &F{}, + G{}, + &G{}, + H{}, + &H{}, + } + enc, err := json.Marshal(t) + fmt.Println(string(enc), err) + + mt := &MT{ + //map[A]bool{A{}: true}, // error + //map[*A]bool{&A{}: true}, // error + //map[B]bool{B{}: true}, // error + //map[*B]bool{&B{}: true}, // error + map[C]bool{C{}: true}, + map[*C]bool{&C{}: true}, + //map[D]bool{D{}: true}, // error + map[*D]bool{&D{}: true}, + map[E]bool{E{}: true}, + map[*E]bool{&E{}: true}, + //map[F]bool{F{}: true}, // error + map[*F]bool{&F{}: true}, + map[G]bool{G{}: true}, + map[*G]bool{&G{}: true}, + //map[H]bool{H{}: true}, // error + map[*H]bool{&H{}: true}, + } + enc, err = json.Marshal(mt) + fmt.Println(string(enc), err) +} diff --git a/v3/internal/generator/collect/collector.go b/v3/internal/generator/collect/collector.go new file mode 100644 index 000000000..e4acd53e8 --- /dev/null +++ b/v3/internal/generator/collect/collector.go @@ -0,0 +1,108 @@ +package collect + +import ( + "go/ast" + "go/types" + "sync" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// Scheduler instances provide task scheduling +// for collection activities. +type Scheduler interface { + // Schedule should run the given function according + // to the implementation's preferred strategy. + // + // Scheduled tasks may call Schedule again; + // therefore, if tasks run concurrently, + // the implementation must support concurrent calls. + Schedule(task func()) +} + +// Info instances provide information about either +// a type-checker object, a struct type or a group of declarations. +type Info interface { + Object() types.Object + Type() types.Type + Node() ast.Node +} + +// Collector wraps all bookkeeping data structures that are needed +// to collect data about a set of packages, bindings and models. +type Collector struct { + // pkgs caches packages that have been registered for collection. + pkgs map[*types.Package]*PackageInfo + + // cache caches collected information about type-checker objects + // and declaration groups. Elements are [Info] instances. + cache sync.Map + + systemPaths *config.SystemPaths + options *flags.GenerateBindingsOptions + scheduler Scheduler + logger config.Logger +} + +// NewCollector initialises a new Collector instance for the given package set. +func NewCollector(pkgs []*packages.Package, systemPaths *config.SystemPaths, options *flags.GenerateBindingsOptions, scheduler Scheduler, logger config.Logger) *Collector { + collector := &Collector{ + pkgs: make(map[*types.Package]*PackageInfo, len(pkgs)), + + systemPaths: systemPaths, + options: options, + scheduler: scheduler, + logger: logger, + } + + // Register packages. + for _, pkg := range pkgs { + collector.pkgs[pkg.Types] = newPackageInfo(pkg, collector) + } + + return collector +} + +// fromCache returns the cached Info instance associated +// to the given type-checker object or declaration group. +// If none exists, a new one is created. +func (collector *Collector) fromCache(objectOrGroup any) Info { + entry, ok := collector.cache.Load(objectOrGroup) + info, _ := entry.(Info) + + if !ok { + switch x := objectOrGroup.(type) { + case *ast.GenDecl, *ast.ValueSpec, *ast.Field: + info = newGroupInfo(x.(ast.Node)) + + case *types.Const: + info = newConstInfo(collector, x) + + case *types.Func: + info = newMethodInfo(collector, x.Origin()) + + case *types.TypeName: + info = newTypeInfo(collector, x) + + case *types.Var: + if !x.IsField() { + panic("cache lookup for invalid object kind") + } + + info = newFieldInfo(collector, x.Origin()) + + case *types.Struct: + info = newStructInfo(collector, x) + + default: + panic("cache lookup for invalid object kind") + } + + entry, _ = collector.cache.LoadOrStore(objectOrGroup, info) + info, _ = entry.(Info) + } + + return info +} diff --git a/v3/internal/generator/collect/const.go b/v3/internal/generator/collect/const.go new file mode 100644 index 000000000..ad624bdad --- /dev/null +++ b/v3/internal/generator/collect/const.go @@ -0,0 +1,104 @@ +package collect + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" + "sync" +) + +// ConstInfo records information about a constant declaration. +// +// Read accesses to any public field are only safe +// if a call to [ConstInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type ConstInfo struct { + Name string + Value any + + Pos token.Pos + Spec *GroupInfo + Decl *GroupInfo + + obj *types.Const + node ast.Node + + collector *Collector + once sync.Once +} + +func newConstInfo(collector *Collector, obj *types.Const) *ConstInfo { + return &ConstInfo{ + obj: obj, + collector: collector, + } +} + +// Const returns the unique ConstInfo instance +// associated to the given object within a collector. +// +// Const is safe for concurrent use. +func (collector *Collector) Const(obj *types.Const) *ConstInfo { + return collector.fromCache(obj).(*ConstInfo) +} + +func (info *ConstInfo) Object() types.Object { + return info.obj +} + +func (info *ConstInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *ConstInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the constant described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ConstInfo) Collect() *ConstInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Value = constant.Val(info.obj.Val()) + + info.Pos = info.obj.Pos() + + path := collector.findDeclaration(info.obj) + if path == nil { + collector.logger.Warningf( + "package %s: const %s: could not find declaration for constant object", + info.obj.Pkg().Path(), + info.Name, + ) + + // Provide dummy groups. + dummyGroup := newGroupInfo(nil).Collect() + info.Spec = dummyGroup + info.Decl = dummyGroup + return + } + + // path shape: *ast.ValueSpec, *ast.GenDecl, *ast.File + info.Spec = collector.fromCache(path[0]).(*GroupInfo).Collect() + info.Decl = collector.fromCache(path[1]).(*GroupInfo).Collect() + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/collect/declaration.go b/v3/internal/generator/collect/declaration.go new file mode 100644 index 000000000..e592b5b1a --- /dev/null +++ b/v3/internal/generator/collect/declaration.go @@ -0,0 +1,238 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/token" + "go/types" + "slices" +) + +// findDeclaration returns the AST spec or declaration +// that defines the given _global_ type-checker object. +// +// Specifically, the first element in the returned slice +// is the relevant spec or declaration, followed by its chain +// of parent nodes up to the declaring [ast.File]. +// +// If no corresponding declaration can be found within +// the set of registered packages, the returned slice is nil. +// +// Resulting node types are as follows: +// - global functions and concrete methods (*types.Func) +// map to *ast.FuncDecl nodes; +// - interface methods from global interfaces (*types.Func) +// map to *ast.Field nodes within their interface expression; +// - struct fields from global structs (*types.Var) +// map to *ast.Field nodes within their struct expression; +// - global constants and variables map to *ast.ValueSpec nodes; +// - global named types map to *ast.TypeSpec nodes; +// - for type parameters, the result is always nil; +// - for local objects defined within functions, +// field types, variable types or field values, +// the result is always nil; +// +// findDeclaration supports unsynchronised concurrent calls. +func (collector *Collector) findDeclaration(obj types.Object) (path []ast.Node) { + pkg := collector.Package(obj.Pkg()).Collect() + if pkg == nil { + return nil + } + + // Perform a binary search to find the file enclosing the node. + // We can't use findEnclosingNode here because it is less accurate and less efficient with files. + fileIndex, exact := slices.BinarySearchFunc(pkg.Files, obj.Pos(), func(f *ast.File, p token.Pos) int { + return cmp.Compare(f.FileStart, p) + }) + + // If exact is true, pkg.Files[fileIndex] is the file we are looking for; + // otherwise, it is the first file whose start position is _after_ obj.Pos(). + if !exact { + fileIndex-- + } + + // When exact is false, the position might lie within an empty segment in between two files. + if fileIndex < 0 || pkg.Files[fileIndex].FileEnd <= obj.Pos() { + return nil + } + + file := pkg.Files[fileIndex] + + // Find enclosing declaration. + decl := findEnclosingNode(file.Decls, obj.Pos()) + if decl == nil { + // Invalid position. + return nil + } + + var gen *ast.GenDecl + + switch d := decl.(type) { + case *ast.FuncDecl: + if obj.Pos() == d.Name.Pos() { + // Object is function. + return []ast.Node{decl, file} + } + + // Ignore local objects defined within function bodies. + return nil + + case *ast.BadDecl: + // What's up?? + return nil + + case *ast.GenDecl: + gen = d + } + + // Handle *ast.GenDecl + + // Find enclosing ast.Spec + spec := findEnclosingNode(gen.Specs, obj.Pos()) + if spec == nil { + // Invalid position. + return nil + } + + var def ast.Expr + + switch s := spec.(type) { + case *ast.ValueSpec: + if s.Names[0].Pos() <= obj.Pos() && obj.Pos() < s.Names[len(s.Names)-1].End() { + // Object is variable or constant. + return []ast.Node{spec, decl, file} + } + + // Ignore local objects defined within variable types/values. + return nil + + case *ast.TypeSpec: + if obj.Pos() == s.Name.Pos() { + // Object is named type. + return []ast.Node{spec, decl, file} + } + + if obj.Pos() < s.Type.Pos() || s.Type.End() <= obj.Pos() { + // Type param or invalid position. + return nil + } + + // Struct or interface field? + def = s.Type + } + + // Handle struct or interface field. + + var iface *ast.InterfaceType + + switch d := def.(type) { + case *ast.StructType: + // Find enclosing field + field := findEnclosingNode(d.Fields.List, obj.Pos()) + if field == nil { + // Invalid position. + return nil + } + + if len(field.Names) == 0 { + // Handle embedded field. + ftype := ast.Unparen(field.Type) + + // Unwrap pointer. + if ptr, ok := ftype.(*ast.StarExpr); ok { + ftype = ast.Unparen(ptr.X) + } + + // Unwrap generic instantiation. + switch t := field.Type.(type) { + case *ast.IndexExpr: + ftype = ast.Unparen(t.X) + case *ast.IndexListExpr: + ftype = ast.Unparen(t.X) + } + + // Unwrap selector. + if sel, ok := ftype.(*ast.SelectorExpr); ok { + ftype = sel.Sel + } + + // ftype must now be an identifier. + if obj.Pos() == ftype.Pos() { + // Object is this embedded field. + return []ast.Node{field, d.Fields, def, spec, decl, file} + } + } else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() { + // Object is one of these fields. + return []ast.Node{field, d.Fields, def, spec, decl, file} + } + + // Ignore local objects defined within field types. + return nil + + case *ast.InterfaceType: + iface = d + + default: + // Other local object or invalid position. + return nil + } + + path = []ast.Node{file, decl, spec, def, iface.Methods} + + // Handle interface method. + for { + field := findEnclosingNode(iface.Methods.List, obj.Pos()) + if field == nil { + // Invalid position. + return nil + } + + path = append(path, field) + + if len(field.Names) == 0 { + // Handle embedded interface. + var ok bool + iface, ok = ast.Unparen(field.Type).(*ast.InterfaceType) + if !ok { + // Not embedded interface, ignore. + return nil + } + + path = append(path, iface, iface.Methods) + // Explore embedded interface. + + } else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() { + // Object is one of these fields. + slices.Reverse(path) + return path + } else { + // Ignore local objects defined within interface method signatures. + return nil + } + } +} + +// findEnclosingNode finds the unique node in nodes, if any, +// that encloses the given position. +// +// It uses binary search and therefore expects +// the nodes slice to be sorted in source order. +func findEnclosingNode[S ~[]E, E ast.Node](nodes S, pos token.Pos) (node E) { + // Perform a binary search to find the nearest node. + index, exact := slices.BinarySearchFunc(nodes, pos, func(n E, p token.Pos) int { + return cmp.Compare(n.Pos(), p) + }) + + // If exact is true, nodes[index] is the node we are looking for; + // otherwise, it is the first node whose start position is _after_ pos. + if !exact { + index-- + } + + // When exact is false, the position might lie within an empty segment in between two nodes. + if index < 0 || nodes[index].End() <= pos { + return // zero value, nil in practice. + } + + return nodes[index] +} diff --git a/v3/internal/generator/collect/directive.go b/v3/internal/generator/collect/directive.go new file mode 100644 index 000000000..05293a640 --- /dev/null +++ b/v3/internal/generator/collect/directive.go @@ -0,0 +1,101 @@ +package collect + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +// IsDirective returns true if the given comment +// is a directive of the form //wails: + directive. +func IsDirective(comment string, directive string) bool { + if strings.HasPrefix(comment, "//wails:"+directive) { + length := len("//wails:") + len(directive) + if len(comment) == length { + return true + } + + next, _ := utf8.DecodeRuneInString(comment[length:]) + return unicode.IsSpace(next) + } + + return false +} + +// ParseDirective extracts the argument portion of a //wails: + directive comment. +func ParseDirective(comment string, directive string) string { + rawArg := comment[len("//wails:")+len(directive):] + + if directive != "inject" { + return strings.TrimSpace(rawArg) + } + + // wails:inject requires special parsing: + // do not trim all surrounding space, just the one space + // immediately after the directive name. + _, wsize := utf8.DecodeRuneInString(rawArg) + return rawArg[wsize:] +} + +// ParseCondition parses an optional two-character condition prefix +// for include or inject directives. +// It returns the argument stripped of the prefix and the resulting condition. +// If the condition is malformed, ParseCondition returns a non-nil error. +func ParseCondition(argument string) (string, Condition, error) { + cond, arg, present := strings.Cut(argument, ":") + if !present { + return cond, Condition{true, true, true, true}, nil + } + + if len(cond) != 2 || !strings.ContainsRune("*jt", rune(cond[0])) || !strings.ContainsRune("*ci", rune(cond[1])) { + return argument, + Condition{true, true, true, true}, + fmt.Errorf("invalid condition code '%s': expected format is '(*|j|t)(*|c|i)'", cond) + } + + condition := Condition{true, true, true, true} + + switch cond[0] { + case 'j': + condition.TS = false + case 't': + condition.JS = false + } + + switch cond[1] { + case 'c': + condition.Interfaces = false + case 'i': + condition.Classes = false + } + + return arg, condition, nil +} + +type Condition struct { + JS bool + TS bool + Classes bool + Interfaces bool +} + +// Satisfied returns true when the condition described by the receiver +// is satisfied by the given configuration. +func (cond Condition) Satisfied(options *flags.GenerateBindingsOptions) bool { + if options.TS { + if options.UseInterfaces { + return cond.TS && cond.Interfaces + } else { + return cond.TS && cond.Classes + } + } else { + if options.UseInterfaces { + return cond.JS && cond.Interfaces + } else { + return cond.JS && cond.Classes + } + } +} diff --git a/v3/internal/generator/collect/field.go b/v3/internal/generator/collect/field.go new file mode 100644 index 000000000..eed808114 --- /dev/null +++ b/v3/internal/generator/collect/field.go @@ -0,0 +1,102 @@ +package collect + +import ( + "go/ast" + "go/token" + "go/types" + "sync" +) + +// FieldInfo records information about a struct field declaration. +// +// Read accesses to any public field are only safe +// if a call to [FieldInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type FieldInfo struct { + Name string + Blank bool + Embedded bool + + Pos token.Pos + Decl *GroupInfo + + obj *types.Var + node ast.Node + + collector *Collector + once sync.Once +} + +// newFieldInfo initialises a descriptor for the given field object. +func newFieldInfo(collector *Collector, obj *types.Var) *FieldInfo { + return &FieldInfo{ + obj: obj, + collector: collector, + } +} + +// Field returns the unique FieldInfo instance +// associated to the given object within a collector. +// +// Field is safe for concurrent use. +func (collector *Collector) Field(obj *types.Var) *FieldInfo { + if !obj.IsField() { + return nil + } + + return collector.fromCache(obj).(*FieldInfo) +} + +func (info *FieldInfo) Object() types.Object { + return info.obj +} + +func (info *FieldInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *FieldInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the struct field +// described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *FieldInfo) Collect() *FieldInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Blank = (info.Name == "" || info.Name == "_") + info.Embedded = info.obj.Embedded() + + info.Pos = info.obj.Pos() + + path := collector.findDeclaration(info.obj) + if path == nil { + // Do not report failure: it is expected for anonymous struct fields. + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.Field, *ast.FieldList, ... + info.Decl = collector.fromCache(path[0]).(*GroupInfo).Collect() + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/collect/group.go b/v3/internal/generator/collect/group.go new file mode 100644 index 000000000..62d83304c --- /dev/null +++ b/v3/internal/generator/collect/group.go @@ -0,0 +1,95 @@ +package collect + +import ( + "go/ast" + "go/token" + "go/types" + "slices" + "sync" +) + +// GroupInfo records information about a group +// of type, field or constant declarations. +// This may be either a list of distinct specifications +// wrapped in parentheses, or a single specification +// declaring multiple fields or constants. +// +// Read accesses to any public field are only safe +// if a call to [GroupInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type GroupInfo struct { + Pos token.Pos + Doc *ast.CommentGroup + + node ast.Node + + once sync.Once +} + +func newGroupInfo(node ast.Node) *GroupInfo { + return &GroupInfo{ + node: node, + } +} + +func (*GroupInfo) Object() types.Object { + return nil +} + +func (*GroupInfo) Type() types.Type { + return nil +} + +func (info *GroupInfo) Node() ast.Node { + return info.node +} + +// Collect gathers information about the declaration group +// described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *GroupInfo) Collect() *GroupInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + switch n := info.node.(type) { + case *ast.GenDecl: + info.Pos = n.Pos() + info.Doc = n.Doc + + case *ast.ValueSpec: + info.Pos = n.Pos() + info.Doc = n.Doc + if info.Doc == nil { + info.Doc = n.Comment + } else if n.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(n.Doc.List, n.Comment.List), + } + } + + case *ast.Field: + info.Pos = n.Pos() + info.Doc = n.Doc + if info.Doc == nil { + info.Doc = n.Comment + } else if n.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(n.Doc.List, n.Comment.List), + } + } + } + }) + + return info +} diff --git a/v3/internal/generator/collect/imports.go b/v3/internal/generator/collect/imports.go new file mode 100644 index 000000000..b62e53963 --- /dev/null +++ b/v3/internal/generator/collect/imports.go @@ -0,0 +1,283 @@ +package collect + +import ( + "go/types" + "path/filepath" + + "golang.org/x/tools/go/types/typeutil" +) + +type ( + // ImportMap records deduplicated imports by a binding or models module. + // It computes relative import paths and assigns import names, + // taking care to avoid collisions. + ImportMap struct { + // Self records the path of the importing package. + Self string + + // ImportModels records whether models from the current package may be needed. + ImportModels bool + + // External records information about each imported package, + // keyed by package path. + External map[string]ImportInfo + + // counters holds the occurence count for each package name in External. + counters map[string]int + collector *Collector + } + + // ImportInfo records information about a single import. + ImportInfo struct { + Name string + Index int // Progressive number for identically named imports, starting from 0 for each distinct name. + RelPath string + } +) + +// NewImportMap initialises an import map for the given importer package. +// The argument may be nil, in which case import paths will be relative +// to the root output directory. +func NewImportMap(importer *PackageInfo) *ImportMap { + var ( + self string + collector *Collector + ) + if importer != nil { + self = importer.Path + collector = importer.collector + } + + return &ImportMap{ + Self: self, + + External: make(map[string]ImportInfo), + + counters: make(map[string]int), + collector: collector, + } +} + +// Merge merges the given import map into the receiver. +// The importing package must be the same. +func (imports *ImportMap) Merge(other *ImportMap) { + if other.Self != imports.Self { + panic("cannot merge import maps with different importing package") + } + + if other.ImportModels { + imports.ImportModels = true + } + + for path, info := range other.External { + if _, ok := imports.External[path]; ok { + continue + } + + counter := imports.counters[info.Name] + imports.counters[info.Name] = counter + 1 + + imports.External[path] = ImportInfo{ + Name: info.Name, + Index: counter, + RelPath: info.RelPath, + } + } +} + +// Add adds the given package to the import map if not already present, +// choosing import names so as to avoid collisions. +// +// Add does not support unsynchronised concurrent calls +// on the same receiver. +func (imports *ImportMap) Add(pkg *PackageInfo) { + if pkg.Path == imports.Self { + // Do not import self. + return + } + + if imports.External[pkg.Path].Name != "" { + // Package already imported. + return + } + + // Fetch and update counter for name. + counter := imports.counters[pkg.Name] + imports.counters[pkg.Name] = counter + 1 + + // Always add counters to + imports.External[pkg.Path] = ImportInfo{ + Name: pkg.Name, + Index: counter, + RelPath: computeImportPath(imports.Self, pkg.Path), + } +} + +// AddType adds all dependencies of the given type to the import map +// and marks all referenced named types as models. +// +// It is a runtime error to call AddType on an ImportMap +// created with nil importing package. +// +// AddType does not support unsynchronised concurrent calls +// on the same receiver. +func (imports *ImportMap) AddType(typ types.Type) { + imports.addTypeImpl(typ, new(typeutil.Map)) +} + +// addTypeImpl provides the actual implementation of AddType. +// The visited parameter is used to break cycles. +func (imports *ImportMap) addTypeImpl(typ types.Type, visited *typeutil.Map) { + collector := imports.collector + if collector == nil { + panic("AddType called on ImportMap with nil collector") + } + + for { // Avoid recursion where possible. + switch t := typ.(type) { + case *types.Alias, *types.Named: + if visited.Set(typ, true) != nil { + // Break type cycles. + return + } + + obj := typ.(interface{ Obj() *types.TypeName }).Obj() + if obj.Pkg() == nil { + // Ignore universe type. + return + } + + if obj.Pkg().Path() == imports.Self { + imports.ImportModels = true + } + + // Record model. + imports.collector.Model(obj) + + // Import parent package. + imports.Add(collector.Package(obj.Pkg())) + + instance, _ := typ.(interface{ TypeArgs() *types.TypeList }) + if instance != nil { + // Record type argument dependencies. + if targs := instance.TypeArgs(); targs != nil { + for i := range targs.Len() { + imports.addTypeImpl(targs.At(i), visited) + } + } + } + + if collector.options.UseInterfaces { + // No creation/initialisation code required. + return + } + + if _, isAlias := typ.(*types.Alias); isAlias { + // Aliased type might be needed during + // JS value creation and initialisation. + typ = types.Unalias(typ) + break + } + + if IsClass(typ) || IsAny(typ) || IsStringAlias(typ) { + return + } + + // If named type does not map to a class, unknown type or string, + // its underlying type may be needed during JS value creation. + typ = typ.Underlying() + + case *types.Basic: + switch { + case t.Info()&(types.IsBoolean|types.IsInteger|types.IsUnsigned|types.IsFloat|types.IsString) != 0: + break + case t.Info()&types.IsComplex != 0: + collector.logger.Warningf("package %s: complex types are not supported by encoding/json", imports.Self) + default: + collector.logger.Warningf("package %s: unknown basic type %s: please report this to Wails maintainers", imports.Self, typ) + } + return + + case *types.Array, *types.Pointer, *types.Slice: + typ = typ.(interface{ Elem() types.Type }).Elem() + + case *types.Chan: + collector.logger.Warningf("package %s: channel types are not supported by encoding/json", imports.Self) + return + + case *types.Map: + if IsMapKey(t.Key()) { + if IsStringAlias(t.Key()) { + // This model type is always rendered as a string alias, + // hence we can generate it and use it as a type for JS object keys. + imports.addTypeImpl(t.Key(), visited) + } + } else if IsTypeParam(t.Key()) { + // In some cases, type params or pointers to type params + // may be valid as map keys, but not for all instantiations. + // When that happens, emit a softer warning. + collector.logger.Warningf( + "package %s: type %s is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors", + imports.Self, types.TypeString(t.Key(), nil), + ) + } else { + collector.logger.Warningf( + "package %s: type %s is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors", + imports.Self, types.TypeString(t.Key(), nil), + ) + } + + typ = t.Elem() + + case *types.Signature: + collector.logger.Warningf("package %s: function types are not supported by encoding/json", imports.Self) + return + + case *types.Struct: + if t.NumFields() == 0 || MaybeJSONMarshaler(typ) != NonMarshaler || MaybeTextMarshaler(typ) != NonMarshaler { + // Struct is empty, or marshals to custom JSON (any) or string. + return + } + + // Retrieve struct info and ensure it is complete. + info := collector.Struct(t).Collect() + + if len(info.Fields) == 0 { + // No visible fields. + return + } + + // Add field dependencies. + for i := range len(info.Fields) - 1 { + imports.addTypeImpl(info.Fields[i].Type, visited) + } + + // Process last field without recursion. + typ = info.Fields[len(info.Fields)-1].Type + + case *types.Interface, *types.TypeParam: + // No dependencies. + return + + default: + collector.logger.Warningf("package %s: unknown type %s: please report this to Wails maintainers", imports.Self, typ) + return + } + } +} + +// computeImportPath returns the shortest relative import path +// through which the importer package can reference the imported one. +func computeImportPath(importer string, imported string) string { + rel, err := filepath.Rel(importer, imported) + if err != nil { + panic(err) + } + + rel = filepath.ToSlash(rel) + if rel[0] == '.' { + return rel + } else { + return "./" + rel + } +} diff --git a/v3/internal/generator/collect/index.go b/v3/internal/generator/collect/index.go new file mode 100644 index 000000000..ae4d31240 --- /dev/null +++ b/v3/internal/generator/collect/index.go @@ -0,0 +1,121 @@ +package collect + +import ( + "slices" + "strings" +) + +// PackageIndex lists all services, models and unexported models +// to be generated from a single package. +// +// When obtained through a call to [PackageInfo.Index], +// each service and model appears at most once; +// services and models are sorted +// by internal property (all exported first), then by name. +type PackageIndex struct { + Package *PackageInfo + + Services []*ServiceInfo + HasExportedServices bool // If true, there is at least one exported service. + + Models []*ModelInfo + HasExportedModels bool // If true, there is at least one exported model. +} + +// Index computes a [PackageIndex] for the selected language from the list +// of generated services and models and regenerates cached stats. +// +// Services and models appear at most once in the returned slices; +// services are sorted by name; +// exported models precede all unexported ones +// and both ranges are sorted by name. +// +// Index calls info.Collect, and therefore provides the same guarantees. +// It is safe for concurrent use. +func (info *PackageInfo) Index(TS bool) (index *PackageIndex) { + // Init index. + index = &PackageIndex{ + Package: info.Collect(), + } + + // Init stats + stats := &Stats{ + NumPackages: 1, + } + + // Gather services. + for _, value := range info.services.Range { + service := value.(*ServiceInfo) + if !service.IsEmpty() { + index.Services = append(index.Services, service) + // Mark presence of exported services + if !service.Internal { + index.HasExportedServices = true + } + // Update service stats. + stats.NumServices++ + stats.NumMethods += len(service.Methods) + } + } + + // Sort services by internal property (exported first), then by name. + slices.SortFunc(index.Services, func(s1 *ServiceInfo, s2 *ServiceInfo) int { + if s1 == s2 { + return 0 + } + + if s1.Internal != s2.Internal { + if s1.Internal { + return 1 + } else { + return -1 + } + } + + return strings.Compare(s1.Name, s2.Name) + }) + + // Gather models. + for _, value := range info.models.Range { + model := value.(*ModelInfo) + index.Models = append(index.Models, model) + // Mark presence of exported models + if !model.Internal { + index.HasExportedModels = true + } + // Update model stats. + if len(model.Values) > 0 { + stats.NumEnums++ + } else { + stats.NumModels++ + } + } + + // Sort models by internal property (exported first), then by name. + slices.SortFunc(index.Models, func(m1 *ModelInfo, m2 *ModelInfo) int { + if m1 == m2 { + return 0 + } + + if m1.Internal != m2.Internal { + if m1.Internal { + return 1 + } else { + return -1 + } + } + + return strings.Compare(m1.Name, m2.Name) + }) + + // Cache stats + info.stats.Store(stats) + + return +} + +// IsEmpty returns true if the given index +// contains no data for the selected language. +func (index *PackageIndex) IsEmpty() bool { + return !index.HasExportedServices && !index.HasExportedModels && len(index.Package.Injections) == 0 +} diff --git a/v3/internal/generator/collect/method.go b/v3/internal/generator/collect/method.go new file mode 100644 index 000000000..adff7aaaa --- /dev/null +++ b/v3/internal/generator/collect/method.go @@ -0,0 +1,115 @@ +package collect + +import ( + "go/ast" + "go/types" + "sync" +) + +// MethodInfo records information about a method declaration. +// +// Read accesses to any public field are only safe +// if a call to [MethodInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type MethodInfo struct { + Name string + + // Abstract is true when the described method belongs to an interface. + Abstract bool + + Doc *ast.CommentGroup + Decl *GroupInfo + + obj *types.Func + node ast.Node + + collector *Collector + once sync.Once +} + +func newMethodInfo(collector *Collector, obj *types.Func) *MethodInfo { + return &MethodInfo{ + obj: obj, + collector: collector, + } +} + +// Method returns the unique MethodInfo instance +// associated to the given object within a collector. +// +// Method is safe for concurrent use. +func (collector *Collector) Method(obj *types.Func) *MethodInfo { + return collector.fromCache(obj).(*MethodInfo) +} + +func (info *MethodInfo) Object() types.Object { + return info.obj +} + +func (info *MethodInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *MethodInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the method described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *MethodInfo) Collect() *MethodInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + + path := collector.findDeclaration(info.obj) + if path == nil { + recv := "" + if info.obj.Type() != nil { + recv = info.obj.Type().(*types.Signature).Recv().Type().String() + "." + } + + collector.logger.Warningf( + "package %s: method %s%s: could not find declaration for method object", + info.obj.Pkg().Path(), + recv, + info.obj.Name(), + ) + + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.FuncDecl/*ast.Field, ... + info.node = path[0] + + // Retrieve doc comments. + switch n := info.node.(type) { + case *ast.FuncDecl: + // Concrete method. + info.Doc = n.Doc + info.Decl = newGroupInfo(nil).Collect() // Provide dummy group. + + case *ast.Field: + // Abstract method. + info.Abstract = true + info.Decl = newGroupInfo(path[0]).Collect() + } + }) + + return info +} diff --git a/v3/internal/generator/collect/model.go b/v3/internal/generator/collect/model.go new file mode 100644 index 000000000..4e5b32cb5 --- /dev/null +++ b/v3/internal/generator/collect/model.go @@ -0,0 +1,381 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/constant" + "go/types" + "slices" + "strings" + "sync" +) + +type ( + // ModelInfo records all information that is required + // to render JS/TS code for a model type. + // + // Read accesses to exported fields are only safe + // if a call to [ModelInfo.Collect] has completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + ModelInfo struct { + *TypeInfo + + // Internal records whether the model + // should be exported by the index file. + Internal bool + + // Imports records dependencies for this model. + Imports *ImportMap + + // Type records the target type for an alias or derived model, + // the underlying type for an enum. + Type types.Type + + // Fields records the property list for a class or struct alias model, + // in order of declaration and grouped by their declaring [ast.Field]. + Fields [][]*ModelFieldInfo + + // Values records the value list for an enum model, + // in order of declaration and grouped + // by their declaring [ast.GenDecl] and [ast.ValueSpec]. + Values [][][]*ConstInfo + + // TypeParams records type parameter names for generic models. + TypeParams []string + + // Predicates caches the value of all type predicates for this model. + // + // WARN: whenever working with a generic uninstantiated model type, + // use these instead of invoking predicate functions, + // which may incur a large performance penalty. + Predicates Predicates + + collector *Collector + once sync.Once + } + + // ModelFieldInfo holds extended information + // about a struct field in a model type. + ModelFieldInfo struct { + *StructField + *FieldInfo + } + + // Predicates caches the value of all type predicates. + Predicates struct { + IsJSONMarshaler MarshalerKind + MaybeJSONMarshaler MarshalerKind + IsTextMarshaler MarshalerKind + MaybeTextMarshaler MarshalerKind + IsMapKey bool + IsTypeParam bool + IsStringAlias bool + IsClass bool + IsAny bool + } +) + +func newModelInfo(collector *Collector, obj *types.TypeName) *ModelInfo { + return &ModelInfo{ + TypeInfo: collector.Type(obj), + collector: collector, + } +} + +// Model retrieves the the unique [ModelInfo] instance +// associated to the given type object within a Collector. +// If none is present, Model initialises a new one +// registers it for code generation +// and schedules background collection activity. +// +// Model is safe for concurrent use. +func (collector *Collector) Model(obj *types.TypeName) *ModelInfo { + pkg := collector.Package(obj.Pkg()) + if pkg == nil { + return nil + } + + model, present := pkg.recordModel(obj) + if !present { + collector.scheduler.Schedule(func() { model.Collect() }) + } + + return model +} + +// Collect gathers information for the model described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ModelInfo) Collect() *ModelInfo { + if info == nil { + return nil + } + + // Changes in the following logic must be reflected adequately + // by the predicates in properties.go, by ImportMap.AddType + // and by all render.Module methods. + + info.once.Do(func() { + collector := info.collector + obj := info.Object().(*types.TypeName) + + typ := obj.Type() + + // Collect type information. + info.TypeInfo.Collect() + + // Initialise import map. + info.Imports = NewImportMap(collector.Package(obj.Pkg())) + + // Setup fallback type. + info.Type = types.Universe.Lookup("any").Type() + + // Record whether the model should be exported. + info.Internal = !obj.Exported() + + // Parse directives. + for _, doc := range []*ast.CommentGroup{info.Doc, info.Decl.Doc} { + if doc == nil { + continue + } + for _, comment := range doc.List { + if IsDirective(comment.Text, "internal") { + info.Internal = true + } + } + } + + // Record type parameter names. + var isGeneric bool + if generic, ok := typ.(interface{ TypeParams() *types.TypeParamList }); ok { + tparams := generic.TypeParams() + isGeneric = tparams != nil + + if isGeneric && tparams.Len() > 0 { + info.TypeParams = make([]string, tparams.Len()) + for i := range tparams.Len() { + info.TypeParams[i] = tparams.At(i).Obj().Name() + } + } + } + + // Precompute predicates. + // Preinstantiate typ to avoid repeated instantiations in predicate code. + ityp := instantiate(typ) + info.Predicates = Predicates{ + IsJSONMarshaler: IsJSONMarshaler(ityp), + MaybeJSONMarshaler: MaybeJSONMarshaler(ityp), + IsTextMarshaler: IsTextMarshaler(ityp), + MaybeTextMarshaler: MaybeTextMarshaler(ityp), + IsMapKey: IsMapKey(ityp), + IsTypeParam: IsTypeParam(ityp), + IsStringAlias: IsStringAlias(ityp), + IsClass: IsClass(ityp), + IsAny: IsAny(ityp), + } + + var def types.Type + var constants []*types.Const + + switch t := typ.(type) { + case *types.Alias: + // Model is an alias: take rhs as definition. + // It is important not to skip alias chains with [types.Unalias] + // because in doing so we could end up with a private type from another package. + def = t.Rhs() + + // Test for constants with alias type, + // but only when non-generic alias resolves to a basic type + // (hence not to e.g. a named type). + if basic, ok := types.Unalias(def).(*types.Basic); ok { + if !isGeneric && basic.Info()&types.IsConstType != 0 && basic.Info()&types.IsComplex == 0 { + // Non-generic alias resolves to a representable constant type: + // look for defined constants whose type is exactly the alias typ. + for _, name := range obj.Pkg().Scope().Names() { + if cnst, ok := obj.Pkg().Scope().Lookup(name).(*types.Const); ok { + alias, isAlias := cnst.Type().(*types.Alias) + if isAlias && cnst.Val().Kind() != constant.Unknown && alias.Obj() == t.Obj() { + constants = append(constants, cnst) + } + } + } + } + } + + case *types.Named: + // Model is a named type: + // jump directly to underlying type to match go semantics, + // i.e. do not render named types as aliases for other named types. + def = typ.Underlying() + + // Check whether it implements marshaler interfaces or has defined constants. + if info.Predicates.MaybeJSONMarshaler != NonMarshaler { + // Type marshals to a custom value of unknown shape. + // If it has explicit custom marshaling logic, render it as any; + // otherwise, delegate to the underlying type that must be the actual [json.Marshaler]. + if info.Predicates.MaybeJSONMarshaler == ExplicitMarshaler { + return + } + } else if info.Predicates.MaybeTextMarshaler != NonMarshaler { + // Type marshals to a custom string of unknown shape. + // If it has explicit custom marshaling logic, render it as string; + // otherwise, delegate to the underlying type that must be the actual [encoding.TextMarshaler]. + // + // One exception must be made for situations + // where the underlying type is a [json.Marshaler] but the model is not: + // in that case, we cannot delegate to the underlying type either. + // Note that in such a case the underlying type is never a pointer or interface, + // because those cannot have explicitly defined methods, + // hence it would not possible for the model not to be a [json.Marshaler] + // while the underlying type is. + if info.Predicates.MaybeTextMarshaler == ExplicitMarshaler || MaybeJSONMarshaler(def) != NonMarshaler { + info.Type = types.Typ[types.String] + return + } + } else if basic, ok := def.Underlying().(*types.Basic); ok { + // Test for enums (excluding marshalers and generic types). + if !isGeneric && basic.Info()&types.IsConstType != 0 && basic.Info()&types.IsComplex == 0 { + // Named type is defined as a representable constant type: + // look for defined constants of that named type. + for _, name := range obj.Pkg().Scope().Names() { + if cnst, ok := obj.Pkg().Scope().Lookup(name).(*types.Const); ok { + if cnst.Val().Kind() != constant.Unknown && types.Identical(cnst.Type(), typ) { + constants = append(constants, cnst) + } + } + } + } + } + + default: + panic("model has unknown object kind (neither alias nor named type)") + } + + // Handle struct types. + strct, isStruct := def.(*types.Struct) + if isStruct && info.Predicates.MaybeJSONMarshaler == NonMarshaler && info.Predicates.MaybeTextMarshaler == NonMarshaler { + // Def is struct and model is not a marshaler: + // collect information about struct fields. + info.collectStruct(strct) + info.Type = nil + return + } + + // Record required imports. + info.Imports.AddType(def) + + // Handle enum types. + // constants slice is always empty for structs, marshalers. + if len(constants) > 0 { + // Collect information about enum values. + info.collectEnum(constants) + } + + // That's all, folks. Render as a TS alias. + info.Type = def + }) + + return info +} + +// collectEnum collects information about enum values and their declarations. +func (info *ModelInfo) collectEnum(constants []*types.Const) { + // Collect information about each constant object. + values := make([]*ConstInfo, len(constants)) + for i, cnst := range constants { + values[i] = info.collector.Const(cnst).Collect() + } + + // Sort values by grouping and source order. + slices.SortFunc(values, func(v1 *ConstInfo, v2 *ConstInfo) int { + // Skip comparisons for identical pointers. + if v1 == v2 { + return 0 + } + + // Sort first by source order of declaration group. + if v1.Decl != v2.Decl { + return cmp.Compare(v1.Decl.Pos, v2.Decl.Pos) + } + + // Then by source order of spec. + if v1.Spec != v2.Spec { + return cmp.Compare(v1.Spec.Pos, v2.Spec.Pos) + } + + // Then by source order of identifiers. + if v1.Pos != v2.Pos { + return cmp.Compare(v1.Pos, v2.Pos) + } + + // Finally by name (for constants whose source position is unknown). + return strings.Compare(v1.Name, v2.Name) + }) + + // Split value list into groups and subgroups. + var decl, spec *GroupInfo + decli, speci := -1, -1 + + for _, value := range values { + if value.Spec != spec { + spec = value.Spec + + if value.Decl == decl { + speci++ + } else { + decl = value.Decl + decli++ + speci = 0 + info.Values = append(info.Values, nil) + } + + info.Values[decli] = append(info.Values[decli], nil) + } + + info.Values[decli][speci] = append(info.Values[decli][speci], value) + } +} + +// collectStruct collects information about struct fields and their declarations. +func (info *ModelInfo) collectStruct(strct *types.Struct) { + collector := info.collector + + // Retrieve struct info. + structInfo := collector.Struct(strct).Collect() + + // Allocate result slice. + fields := make([]*ModelFieldInfo, len(structInfo.Fields)) + + // Collect fields. + for i, field := range structInfo.Fields { + // Record required imports. + info.Imports.AddType(field.Type) + + fields[i] = &ModelFieldInfo{ + StructField: field, + FieldInfo: collector.Field(field.Object).Collect(), + } + } + + // Split field list into groups, preserving the original order. + var decl *GroupInfo + decli := -1 + + for _, field := range fields { + if field.Decl != decl { + decl = field.Decl + decli++ + info.Fields = append(info.Fields, nil) + } + + info.Fields[decli] = append(info.Fields[decli], field) + } +} diff --git a/v3/internal/generator/collect/package.go b/v3/internal/generator/collect/package.go new file mode 100644 index 000000000..b03d5e577 --- /dev/null +++ b/v3/internal/generator/collect/package.go @@ -0,0 +1,293 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/token" + "go/types" + "path/filepath" + "slices" + "strings" + "sync" + "sync/atomic" + + "golang.org/x/tools/go/packages" +) + +// PackageInfo records information about a package. +// +// Read accesses to fields Path, Name, Types, TypesInfo, Fset +// are safe at any time without any synchronisation. +// +// Read accesses to all other fields are only safe +// if a call to [PackageInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +// +// Concurrent write accesses are only allowed through the provided methods. +type PackageInfo struct { + // Path holds the canonical path of the described package. + Path string + + // Name holds the import name of the described package. + Name string + + // Types and TypesInfo hold type information for this package. + Types *types.Package + TypesInfo *types.Info + + // Fset holds the FileSet that was used to parse this package. + Fset *token.FileSet + + // Files holds parsed files for this package, + // ordered by start position to support binary search. + Files []*ast.File + + // Docs holds package doc comments. + Docs []*ast.CommentGroup + + // Includes holds a list of additional files to include + // with the generated bindings. + // It maps file names to their paths on disk. + Includes map[string]string + + // Injections holds a list of code lines to be injected + // into the package index file. + Injections []string + + // services records service types that have to be generated for this package. + // We rely upon [sync.Map] for atomic swapping support. + // Keys are *types.TypeName, values are *ServiceInfo. + services sync.Map + + // models records model types that have to be generated for this package. + // We rely upon [sync.Map] for atomic swapping support. + // Keys are *types.TypeName, values are *ModelInfo. + models sync.Map + + // stats caches statistics about this package. + stats atomic.Pointer[Stats] + + collector *Collector + once sync.Once +} + +func newPackageInfo(pkg *packages.Package, collector *Collector) *PackageInfo { + return &PackageInfo{ + Path: pkg.PkgPath, + Name: pkg.Name, + + Types: pkg.Types, + TypesInfo: pkg.TypesInfo, + + Fset: pkg.Fset, + Files: pkg.Syntax, + + collector: collector, + } +} + +// Package retrieves the the unique [PackageInfo] instance, if any, +// associated to the given package object within a Collector. +// +// Package is safe for concurrent use. +func (collector *Collector) Package(pkg *types.Package) *PackageInfo { + return collector.pkgs[pkg] +} + +// Iterate calls yield sequentially for each [PackageInfo] instance +// registered with the collector. If yield returns false, +// Iterate stops the iteration. +// +// Iterate is safe for concurrent use. +func (collector *Collector) Iterate(yield func(pkg *PackageInfo) bool) { + for _, pkg := range collector.pkgs { + if !yield(pkg) { + return + } + } +} + +// Stats returns cached statistics for this package. +// If [PackageInfo.Index] has not been called yet, it returns nil. +// +// Stats is safe for unsynchronised concurrent calls. +func (info *PackageInfo) Stats() *Stats { + return info.stats.Load() +} + +// Collect gathers information about the package described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *PackageInfo) Collect() *PackageInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + // Sort files by source position. + if !slices.IsSortedFunc(info.Files, compareAstFiles) { + info.Files = slices.Clone(info.Files) + slices.SortFunc(info.Files, compareAstFiles) + } + + // Collect docs and parse directives. + for _, file := range info.Files { + if file.Doc == nil { + continue + } + + info.Docs = append(info.Docs, file.Doc) + + // Retrieve file directory. + pos := info.Fset.Position(file.Pos()) + if !pos.IsValid() { + collector.logger.Errorf( + "package %s: found AST file with unknown path: `wails:include` directives from that file will be ignored", + info.Path, + ) + } + dir := filepath.Dir(pos.Filename) + + // Parse directives. + if info.Includes == nil { + info.Includes = make(map[string]string) + } + for _, comment := range file.Doc.List { + switch { + case IsDirective(comment.Text, "inject"): + // Check condition. + line, cond, err := ParseCondition(ParseDirective(comment.Text, "inject")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:inject` directive: %v", + info.Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Record injected line. + info.Injections = append(info.Injections, line) + + case pos.IsValid() && IsDirective(comment.Text, "include"): + // Check condition. + pattern, cond, err := ParseCondition(ParseDirective(comment.Text, "include")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:include` directive: %v", + info.Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Collect matching files. + paths, err := filepath.Glob(filepath.Join(dir, pattern)) + if err != nil { + collector.logger.Errorf( + "%s: invalid pattern '%s' in `wails:include` directive: %v", + info.Fset.Position(comment.Pos()), + pattern, + err, + ) + continue + } else if len(paths) == 0 { + collector.logger.Warningf( + "%s: pattern '%s' in `wails:include` directive matched no files", + info.Fset.Position(comment.Pos()), + pattern, + ) + continue + } + + // Announce and record matching files. + for _, path := range paths { + name := strings.ToLower(filepath.Base(path)) + if old, ok := info.Includes[name]; ok { + collector.logger.Errorf( + "%s: duplicate included file name '%s' in package %s; old path: '%s'; new path: '%s'", + info.Fset.Position(comment.Pos()), + name, + info.Path, + old, + path, + ) + continue + } + + collector.logger.Debugf( + "including file '%s' as '%s' in package %s", + path, + name, + info.Path, + ) + + info.Includes[name] = path + } + } + } + } + }) + + return info +} + +// recordService adds the given service type object +// to the set of bindings generated for this package. +// It returns the unique [ServiceInfo] instance associated +// with the given type object. +// +// It is an error to pass in here a type whose parent package +// is not the one described by the receiver. +// +// recordService is safe for unsynchronised concurrent calls. +func (info *PackageInfo) recordService(obj *types.TypeName) *ServiceInfo { + // Fetch current value, then add if not already present. + service, _ := info.services.Load(obj) + if service == nil { + service, _ = info.services.LoadOrStore(obj, newServiceInfo(info.collector, obj)) + } + return service.(*ServiceInfo) +} + +// recordModel adds the given model type object +// to the set of models generated for this package. +// It returns the unique [ModelInfo] instance associated +// with the given type object. The present result is true +// if the model was already registered. +// +// It is an error to pass in here a type whose parent package +// is not the one described by the receiver. +// +// recordModel is safe for unsynchronised concurrent calls. +func (info *PackageInfo) recordModel(obj *types.TypeName) (model *ModelInfo, present bool) { + // Fetch current value, then add if not already present. + imodel, present := info.models.Load(obj) + if imodel == nil { + imodel, present = info.models.LoadOrStore(obj, newModelInfo(info.collector, obj)) + } + return imodel.(*ModelInfo), present +} + +// compareAstFiles compares two AST files by starting position. +func compareAstFiles(f1 *ast.File, f2 *ast.File) int { + return cmp.Compare(f1.FileStart, f2.FileStart) +} diff --git a/v3/internal/generator/collect/predicates.go b/v3/internal/generator/collect/predicates.go new file mode 100644 index 000000000..bcb910186 --- /dev/null +++ b/v3/internal/generator/collect/predicates.go @@ -0,0 +1,478 @@ +package collect + +// This file gathers functions that test useful properties of model types. +// The rationale for the way things are handled here +// is given in the example file found at ./_reference/json_marshaler_behaviour.go + +import ( + "go/token" + "go/types" + "iter" + + "golang.org/x/exp/typeparams" +) + +// Cached interface types. +var ( + ifaceTextMarshaler = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalText", + types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), false)), + }, nil).Complete() + + ifaceJSONMarshaler = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalJSON", + types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), false)), + }, nil).Complete() +) + +// MarshalerKind values describe +// whether and how a type implements a marshaler interface. +// For any one of the two marshaler interfaces, a type is +// - a NonMarshaler if it does not implement it; +// - an ImplicitMarshaler if it inherits the implementation from its underlying type; +// - an ExplicitMarshaler if it defines the relevant method explicitly. +type MarshalerKind byte + +const ( + NonMarshaler MarshalerKind = iota + ImplicitMarshaler + ExplicitMarshaler +) + +// termlist returns an iterator over the normalised term list of the given type. +// If typ is invalid or has an empty type set, termlist returns the empty sequence. +// If typ has an empty term list +// then termlist returns a sequence with just one element: the type itself. +// +// TODO: replace with new term set API once Go 1.25 is out. +// See go.dev/issue/61013 +func termlist(typ types.Type) iter.Seq[*typeparams.Term] { + terms, err := typeparams.NormalTerms(types.Unalias(typ)) + return func(yield func(*typeparams.Term) bool) { + if err == nil && len(terms) == 0 { + yield(typeparams.NewTerm(false, typ)) + } else { + for _, term := range terms { + if !yield(term) { + break + } + } + } + } +} + +// instantiate instantiates typ if it is an uninstantiated generic type +// using its own type parameters as arguments in order to preserve genericity. +// +// If typ is not generic or already instantiated, it is returned as is. +// If typ is not an alias, then the returned type is not an alias either. +func instantiate(typ types.Type) types.Type { + if t, ok := typ.(interface { + TypeParams() *types.TypeParamList + TypeArgs() *types.TypeList + }); ok && t.TypeParams() != nil && t.TypeArgs() == nil { + args := make([]types.Type, t.TypeParams().Len()) + for i := range args { + args[i] = t.TypeParams().At(i) + } + + typ, _ = types.Instantiate(nil, typ, args, false) + } + + return typ +} + +// isMarshaler checks whether the given type +// implements one of the two marshaler interfaces, +// and whether it implements it explicitly, +// i.e. by defining the relevant method directly +// instead of inheriting it from the underlying type. +// +// If addressable is true, it checks both pointer and non-pointer receivers. +// +// The behaviour of isMarshaler is unspecified +// if marshaler is not one of [json.Marshaler] or [encoding.TextMarshaler]. +func isMarshaler(typ types.Type, marshaler *types.Interface, addressable bool, visited map[*types.TypeName]MarshalerKind) MarshalerKind { + // Follow alias chain and instantiate if necessary. + // + // types.Implements does not handle generics, + // hence when typ is generic it must be instantiated. + // + // Instantiation operations may incur a large performance penalty and are usually cached, + // but doing so here would entail some complex global state and a potential memory leak. + // Because typ should be generic only during model collection, + // it should be enough to cache the result of marshaler queries for models. + typ = instantiate(types.Unalias(typ)) + + // Invariant: at this point, typ is not an alias. + + if typ == types.Typ[types.Invalid] { + // Do not pass invalid types to [types.Implements]. + return NonMarshaler + } + + result := types.Implements(typ, marshaler) + + ptr, isPtr := typ.Underlying().(*types.Pointer) + + if !result && addressable && !isPtr { + result = types.Implements(types.NewPointer(typ), marshaler) + } + + named, isNamed := typ.(*types.Named) + + if result { + // Check whether marshaler method is implemented explicitly on a named type. + if isNamed { + method := marshaler.Method(0).Name() + for i := range named.NumMethods() { + if named.Method(i).Name() == method { + return ExplicitMarshaler + } + } + } + + return ImplicitMarshaler + } + + // Fast path: named types that fail the [types.Implements] test cannot be marshalers. + // + // WARN: currently typeparams cannot be used on the rhs of a named type declaration. + // If that changes in the future, + // this guard will become essential for correctness, + // not just a shortcut. + if isNamed { + return NonMarshaler + } + + // Unwrap at most one pointer and follow alias chain. + if isPtr { + typ = types.Unalias(ptr.Elem()) + } + + // Invariant: at this point, typ is not an alias. + + // Type parameters require special handling: + // iterate over their term list and treat them as marshalers + // if so are all their potential instantiations. + + tp, ok := typ.(*types.TypeParam) + if !ok { + return NonMarshaler + } + + // Init cycle detection/deduplication map. + if visited == nil { + visited = make(map[*types.TypeName]MarshalerKind) + } + + // Type params cannot be embedded in constraints directly, + // but they can be embedded as pointer terms. + // + // When we hit that kind of cycle, + // we can err towards it being a marshaler: + // such a constraint is meaningless anyways, + // as no type can be simultaneously a pointer to itself. + // + // Therefore, we iterate the type set + // only for unvisited pointers-to-typeparams, + // and return the current best guess + // for those we have already visited. + // + // WARN: there has been some talk + // of allowing type parameters as embedded fields/terms. + // That might make our lives miserable here. + // The spec must be monitored for changes in that regard. + if isPtr { + if kind, ok := visited[tp.Obj()]; ok { + return kind + } + } + + // Initialise kind to explicit marshaler, then decrease as needed. + kind := ExplicitMarshaler + + if isPtr { + // Pointers are never explicit marshalers. + kind = ImplicitMarshaler + // Mark pointer-to-typeparam as visited and init current best guess. + visited[tp.Obj()] = kind + } + + // Iterate term list. + for term := range termlist(tp) { + ttyp := types.Unalias(term.Type()) + + // Reject if tp has a tilde or invalid element in its term list + // or has a method-only constraint. + // + // Valid tilde terms + // can always be satisfied by named types that hide their methods + // hence fail in general to implement the required interface. + if term.Tilde() || ttyp == types.Typ[types.Invalid] || ttyp == tp { + kind = NonMarshaler + break + } + + // Propagate the presence of a wrapping pointer. + if isPtr { + ttyp = types.NewPointer(ttyp) + } + + kind = min(kind, isMarshaler(ttyp, marshaler, addressable && !isPtr, visited)) + if kind == NonMarshaler { + // We can stop here as we've reached the minimum [MarshalerKind]. + break + } + } + + // Store final response for pointer-to-typeparam. + if isPtr { + visited[tp.Obj()] = kind + } + + return kind +} + +// IsTextMarshaler queries whether and how the given type +// implements the [encoding.TextMarshaler] interface. +func IsTextMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceTextMarshaler, false, nil) +} + +// MaybeTextMarshaler queries whether and how the given type +// implements the [encoding.TextMarshaler] interface for at least one receiver form. +func MaybeTextMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceTextMarshaler, true, nil) +} + +// IsJSONMarshaler queries whether and how the given type +// implements the [json.Marshaler] interface. +func IsJSONMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceJSONMarshaler, false, nil) +} + +// MaybeJSONMarshaler queries whether and how the given type +// implements the [json.Marshaler] interface for at least one receiver form. +func MaybeJSONMarshaler(typ types.Type) MarshalerKind { + return isMarshaler(typ, ifaceJSONMarshaler, true, nil) +} + +// IsMapKey returns true if the given type +// is accepted as a map key by encoding/json. +func IsMapKey(typ types.Type) bool { + // Iterate over type set and return true if all elements are valid. + // + // We cannot simply delegate to [IsTextMarshaler] here + // because a union of some basic terms and some TextMarshalers + // might still be acceptable. + // + // NOTE: If typ is not a typeparam or constraint, termlist returns just typ itself. + // If typ has an empty type set, it's safe to return true + // because the map cannot be instantiated anyways. + for term := range termlist(typ) { + ttyp := types.Unalias(term.Type()) + + // Types whose underlying type is a signed/unsigned integer or a string + // are always acceptable, whether they are marshalers or not. + if basic, ok := ttyp.Underlying().(*types.Basic); ok { + if basic.Info()&(types.IsInteger|types.IsUnsigned|types.IsString) != 0 { + continue + } + } + + // Valid tilde terms + // can always be satisfied by named types that hide their methods + // hence fail in general to implement the required interface. + // For example one could have: + // + // type NotAKey struct{ encoding.TextMarshaler } + // func (NotAKey) MarshalText() int { ... } + // + // which satisfies ~struct{ encoding.TextMarshaler } + // but is not itself a TextMarshaler. + // + // It might still be the case that the constraint + // requires explicitly a marshaling method, + // hence we perform one last check on typ. + // + // For example, we reject interface{ ~struct{ ... } } + // but still accept interface{ ~struct{ ... }; MarshalText() ([]byte, error) } + // + // All other cases are only acceptable + // if the type implements [encoding.TextMarshaler] in non-addressable mode. + if term.Tilde() || IsTextMarshaler(ttyp) == NonMarshaler { + // When some term fails, test the input typ itself, + // but only if it has not been tested already. + // + // Note that when term.Tilde() is true + // then it is always the case that typ != term.Type(), + // because cyclic constraints are not allowed + // and naked type parameters cannot occur in type unions. + return typ != term.Type() && IsTextMarshaler(typ) != NonMarshaler + } + } + + return true +} + +// IsTypeParam returns true when the given type +// is either a TypeParam or a pointer to a TypeParam. +func IsTypeParam(typ types.Type) bool { + switch t := types.Unalias(typ).(type) { + case *types.TypeParam: + return true + case *types.Pointer: + _, ok := types.Unalias(t.Elem()).(*types.TypeParam) + return ok + default: + return false + } +} + +// IsStringAlias returns true when +// either typ will be rendered to JS/TS as an alias for the TS type `string`, +// or typ itself (not its underlying type) is a pointer +// whose element type satisfies the property described above. +// +// This predicate is only safe to use either with map keys, +// where pointers are treated in an ad-hoc way by [json.Marshal], +// or when typ IS ALREADY KNOWN to be either [types.Alias] or [types.Named]. +// +// Otherwise, the result might be incorrect: +// IsStringAlias MUST NOT be used to check +// whether an arbitrary instance of [types.Type] +// renders as a JS/TS string type. +// +// Notice that IsStringAlias returns false for all type parameters: +// detecting those that must be always instantiated as string aliases +// is technically possible, but very difficult. +func IsStringAlias(typ types.Type) bool { + // Unwrap at most one pointer. + // NOTE: do not unalias typ before testing: + // aliases whose underlying type is a pointer + // are never rendered as strings. + if ptr, ok := typ.(*types.Pointer); ok { + typ = ptr.Elem() + } + + switch typ.(type) { + case *types.Alias, *types.Named: + // Aliases and named types might be rendered as string aliases. + default: + // Not a model type, hence not an alias. + return false + } + + // Skip pointer and interface types: they are always nullable + // and cannot have any explicitly defined methods. + // This takes care of rejecting type params as well, + // since their underlying type is guaranteed to be an interface. + switch typ.Underlying().(type) { + case *types.Pointer, *types.Interface: + return false + } + + // Follow alias chain. + typ = types.Unalias(typ) + + // Aliases of the basic string type are rendered as strings. + if basic, ok := typ.(*types.Basic); ok { + return basic.Info()&types.IsString != 0 + } + + // json.Marshalers can only be rendered as any. + // TextMarshalers that aren't json.Marshalers render as strings. + if MaybeJSONMarshaler(typ) != NonMarshaler { + return false + } else if MaybeTextMarshaler(typ) != NonMarshaler { + return true + } + + // Named types whose underlying type is a string are rendered as strings. + basic, ok := typ.Underlying().(*types.Basic) + return ok && basic.Info()&types.IsString != 0 +} + +// IsClass returns true if the given type will be rendered +// as a JS/TS model class (or interface). +func IsClass(typ types.Type) bool { + // Follow alias chain. + typ = types.Unalias(typ) + + if _, isNamed := typ.(*types.Named); !isNamed { + // Unnamed types are never rendered as classes. + return false + } + + // Struct named types without custom marshaling are rendered as classes. + _, isStruct := typ.Underlying().(*types.Struct) + return isStruct && MaybeJSONMarshaler(typ) == NonMarshaler && MaybeTextMarshaler(typ) == NonMarshaler +} + +// IsAny returns true if the given type +// is guaranteed to render as the TS any type or equivalent. +// +// It might return false negatives for generic aliases, +// hence should only be used with instantiated types +// or in contexts where false negatives are acceptable. +func IsAny(typ types.Type) bool { + // Follow alias chain. + typ = types.Unalias(typ) + + if MaybeJSONMarshaler(typ) != NonMarshaler { + // If typ is either a named type, an interface, a pointer or a struct, + // it will be rendered as (possibly an alias for) the TS any type. + // + // If it is a type parameter that implements json.Marshal, + // every possible concrete instantiation will implement json.Marshal, + // hence will be rendered as the TS any type. + return true + } + + if MaybeTextMarshaler(typ) != NonMarshaler { + // If type is either a named type, an interface, a pointer or a struct, + // it will be rendered as (possibly an alias for) + // the (possibly nullable) TS string type. + // + // If typ is a type parameter, we know at this point + // that it does not necessarily implement json.Marshaler, + // hence it will be possible to instantiate it in a way + // that renders as the (possibly nullable) TS string type. + return false + } + + if ptr, ok := typ.Underlying().(*types.Pointer); ok { + // Pointers render as the union of their element type with null. + // This is equivalent to the TS any type + // if and only if so is the element type. + return IsAny(ptr.Elem()) + } + + // All types listed below have rich TS equivalents, + // hence won't be equivalent to the TS any type. + // + // WARN: it is important to keep these lists explicit and up to date + // instead of listing the unsupported types (which would be much easier). + // + // By doing so, IsAny will keep working correctly + // in case future updates to the Go spec introduce new type families, + // thus buying the maintainers some time to patch the binding generator. + + // Retrieve underlying type. + switch t := typ.Underlying().(type) { + case *types.Basic: + // Complex types are not supported. + return t.Info()&(types.IsBoolean|types.IsInteger|types.IsUnsigned|types.IsFloat|types.IsString) == 0 + case *types.Array, *types.Slice, *types.Map, *types.Struct, *types.TypeParam: + return false + } + + return true +} diff --git a/v3/internal/generator/collect/service.go b/v3/internal/generator/collect/service.go new file mode 100644 index 000000000..ba486cc37 --- /dev/null +++ b/v3/internal/generator/collect/service.go @@ -0,0 +1,332 @@ +package collect + +import ( + "fmt" + "go/ast" + "go/types" + "strconv" + "sync" + + "github.com/wailsapp/wails/v3/internal/hash" + "golang.org/x/tools/go/types/typeutil" +) + +type ( + // ServiceInfo records all information that is required + // to render JS/TS code for a service type. + // + // Read accesses to any public field are only safe + // if a call to [ServiceInfo.Collect] has completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + ServiceInfo struct { + *TypeInfo + + // Internal records whether the service + // should be exported by the index file. + Internal bool + + Imports *ImportMap + Methods []*ServiceMethodInfo + + // HasInternalMethods records whether the service + // defines lifecycle or http server methods. + HasInternalMethods bool + + // Injections stores a list of JS code lines + // that should be injected into the generated file. + Injections []string + + collector *Collector + once sync.Once + } + + // ServiceMethodInfo records all information that is required + // to render JS/TS code for a service method. + ServiceMethodInfo struct { + *MethodInfo + FQN string + ID string + Internal bool + Params []*ParamInfo + Results []types.Type + } + + // ParamInfo records all information that is required + // to render JS/TS code for a service method parameter. + ParamInfo struct { + Name string + Type types.Type + Blank bool + Variadic bool + } +) + +func newServiceInfo(collector *Collector, obj *types.TypeName) *ServiceInfo { + return &ServiceInfo{ + TypeInfo: collector.Type(obj), + collector: collector, + } +} + +// Service returns the unique ServiceInfo instance +// associated to the given object within a collector +// and registers it for code generation. +// +// Service is safe for concurrent use. +func (collector *Collector) Service(obj *types.TypeName) *ServiceInfo { + pkg := collector.Package(obj.Pkg()) + if pkg == nil { + return nil + } + + return pkg.recordService(obj) +} + +// IsEmpty returns true if no methods or code injections +// are present for this service, for the selected language. +func (info *ServiceInfo) IsEmpty() bool { + // Ensure information has been collected. + info.Collect() + return len(info.Methods) == 0 && len(info.Injections) == 0 +} + +// Collect gathers information about the service described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *ServiceInfo) Collect() *ServiceInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + obj := info.Object().(*types.TypeName) + + // Collect type information. + info.TypeInfo.Collect() + + // Initialise import map. + info.Imports = NewImportMap(collector.Package(obj.Pkg())) + + // Compute intuitive method set (i.e. both pointer and non-pointer receiver). + // Do not use a method set cache because + // - it would hurt concurrency (requires mutual exclusion), + // - it is only useful when the same type is queried many times; + // this may only happen here if some embedded types appear frequently, + // which should be far from average. + mset := typeutil.IntuitiveMethodSet(obj.Type(), nil) + + // Collect method information. + info.Methods = make([]*ServiceMethodInfo, 0, len(mset)) + for _, sel := range mset { + switch { + case internalServiceMethods[sel.Obj().Name()]: + info.HasInternalMethods = true + continue + case !sel.Obj().Exported(): + // Ignore unexported and internal methods. + continue + } + + methodInfo := info.collectMethod(sel.Obj().(*types.Func)) + if methodInfo != nil { + info.Methods = append(info.Methods, methodInfo) + } + } + + // Record whether the service should be exported. + info.Internal = !obj.Exported() + + // Parse directives. + for _, doc := range []*ast.CommentGroup{info.Doc, info.Decl.Doc} { + if doc == nil { + continue + } + for _, comment := range doc.List { + switch { + case IsDirective(comment.Text, "internal"): + info.Internal = true + + case IsDirective(comment.Text, "inject"): + // Check condition. + line, cond, err := ParseCondition(ParseDirective(comment.Text, "inject")) + if err != nil { + collector.logger.Errorf( + "%s: in `wails:inject` directive: %v", + collector.Package(obj.Pkg()).Fset.Position(comment.Pos()), + err, + ) + continue + } + + if !cond.Satisfied(collector.options) { + continue + } + + // Record injected line. + info.Injections = append(info.Injections, line) + } + } + } + }) + + return info +} + +// internalServiceMethod is a set of methods +// that are handled specially by the binding engine +// and must not be exposed to the frontend. +var internalServiceMethods = map[string]bool{ + "ServiceName": true, + "ServiceStartup": true, + "ServiceShutdown": true, + "ServeHTTP": true, +} + +// typeError caches the type-checker type for the Go error interface. +var typeError = types.Universe.Lookup("error").Type() + +// typeAny caches the empty interface type. +var typeAny = types.Universe.Lookup("any").Type().Underlying() + +// collectMethod collects and returns information about a service method. +// It is intended to be called only by ServiceInfo.Collect. +func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo { + collector := info.collector + obj := info.Object().(*types.TypeName) + + signature, _ := method.Type().(*types.Signature) + if signature == nil { + // Skip invalid interface method. + // TODO: is this actually necessary? + return nil + } + + // Compute fully qualified name. + path := obj.Pkg().Path() + if obj.Pkg().Name() == "main" { + // reflect.Method.PkgPath is always "main" for the main package. + // This should not cause collisions because + // other main packages are not importable. + path = "main" + } + + fqn := path + "." + obj.Name() + "." + method.Name() + id := hash.Fnv(fqn) + + methodInfo := &ServiceMethodInfo{ + MethodInfo: collector.Method(method).Collect(), + FQN: fqn, + ID: strconv.FormatUint(uint64(id), 10), + Params: make([]*ParamInfo, 0, signature.Params().Len()), + Results: make([]types.Type, 0, signature.Results().Len()), + } + + // Parse directives. + if methodInfo.Doc != nil { + var methodIdFound bool + + for _, comment := range methodInfo.Doc.List { + switch { + case IsDirective(comment.Text, "ignore"): + return nil + + case IsDirective(comment.Text, "internal"): + methodInfo.Internal = true + + case !methodIdFound && IsDirective(comment.Text, "id"): + idString := ParseDirective(comment.Text, "id") + idValue, err := strconv.ParseUint(idString, 10, 32) + + if err != nil { + collector.logger.Errorf( + "%s: invalid value '%s' in `wails:id` directive: expected a valid uint32 value", + collector.Package(method.Pkg()).Fset.Position(comment.Pos()), + idString, + ) + continue + } + + // Announce and record alias. + collector.logger.Infof( + "package %s: method %s.%s: default ID %s replaced by %d", + path, + obj.Name(), + method.Name(), + methodInfo.ID, + idValue, + ) + methodInfo.ID = strconv.FormatUint(idValue, 10) + methodIdFound = true + } + } + } + + // Collect parameters. + for i := range signature.Params().Len() { + param := signature.Params().At(i) + + if i == 0 { + // Skip first parameter if it has context type. + named, ok := types.Unalias(param.Type()).(*types.Named) + if ok && named.Obj().Pkg().Path() == collector.systemPaths.ContextPackage && named.Obj().Name() == "Context" { + continue + } + } + + if types.IsInterface(param.Type()) && !types.Identical(param.Type(), typeAny) { + paramName := param.Name() + if paramName == "" || paramName == "_" { + paramName = fmt.Sprintf("#%d", i+1) + } + + collector.logger.Warningf( + "%s: parameter %s has non-empty interface type %s: this is not supported by encoding/json and will likely result in runtime errors", + collector.Package(method.Pkg()).Fset.Position(param.Pos()), + paramName, + param.Type(), + ) + } + + // Record type dependencies. + info.Imports.AddType(param.Type()) + + // Record parameter. + methodInfo.Params = append(methodInfo.Params, &ParamInfo{ + Name: param.Name(), + Type: param.Type(), + Blank: param.Name() == "" || param.Name() == "_", + }) + } + + if signature.Variadic() { + methodInfo.Params[len(methodInfo.Params)-1].Type = methodInfo.Params[len(methodInfo.Params)-1].Type.(*types.Slice).Elem() + methodInfo.Params[len(methodInfo.Params)-1].Variadic = true + } + + // Collect results. + for i := range signature.Results().Len() { + result := signature.Results().At(i) + + if types.Identical(result.Type(), typeError) { + // Skip error results, they are thrown as exceptions + continue + } + + // Record type dependencies. + info.Imports.AddType(result.Type()) + + // Record result. + methodInfo.Results = append(methodInfo.Results, result.Type()) + } + + return methodInfo +} diff --git a/v3/internal/generator/collect/stats.go b/v3/internal/generator/collect/stats.go new file mode 100644 index 000000000..984c7e504 --- /dev/null +++ b/v3/internal/generator/collect/stats.go @@ -0,0 +1,34 @@ +package collect + +import "time" + +type Stats struct { + NumPackages int + NumServices int + NumMethods int + NumEnums int + NumModels int + StartTime time.Time + EndTime time.Time +} + +func (stats *Stats) Start() { + stats.StartTime = time.Now() + stats.EndTime = stats.StartTime +} + +func (stats *Stats) Stop() { + stats.EndTime = time.Now() +} + +func (stats *Stats) Elapsed() time.Duration { + return stats.EndTime.Sub(stats.StartTime) +} + +func (stats *Stats) Add(other *Stats) { + stats.NumPackages += other.NumPackages + stats.NumServices += other.NumServices + stats.NumMethods += other.NumMethods + stats.NumEnums += other.NumEnums + stats.NumModels += other.NumModels +} diff --git a/v3/internal/generator/collect/struct.go b/v3/internal/generator/collect/struct.go new file mode 100644 index 000000000..b9a1b99ca --- /dev/null +++ b/v3/internal/generator/collect/struct.go @@ -0,0 +1,352 @@ +package collect + +import ( + "cmp" + "go/ast" + "go/types" + "reflect" + "slices" + "strings" + "sync" + "unicode" +) + +type ( + // StructInfo records the flattened field list for a struct type, + // taking into account JSON tags. + // + // The field list is initially empty. It will be populated + // upon calling [StructInfo.Collect] for the first time. + // + // Read accesses to the field list are only safe + // if a call to [StructInfo.Collect] has been completed before the access, + // for example by calling it in the accessing goroutine + // or before spawning the accessing goroutine. + StructInfo struct { + Fields []*StructField + + typ *types.Struct + + collector *Collector + once sync.Once + } + + // FieldInfo represents a single field in a struct. + StructField struct { + JsonName string // Avoid collisions with [FieldInfo.Name]. + Type types.Type + Optional bool + Quoted bool + + // Object holds the described type-checker object. + Object *types.Var + } +) + +func newStructInfo(collector *Collector, typ *types.Struct) *StructInfo { + return &StructInfo{ + typ: typ, + collector: collector, + } +} + +// Struct retrieves the unique [StructInfo] instance +// associated to the given type within a Collector. +// If none is present, a new one is initialised. +// +// Struct is safe for concurrent use. +func (collector *Collector) Struct(typ *types.Struct) *StructInfo { + // Cache by type pointer, do not use a typeutil.Map: + // - for models, it may result in incorrect comments; + // - for anonymous structs, it would probably bring little benefit + // because the probability of repetitions is much lower. + + return collector.fromCache(typ).(*StructInfo) +} + +func (*StructInfo) Object() types.Object { + return nil +} + +func (info *StructInfo) Type() types.Type { + return info.typ +} + +func (*StructInfo) Node() ast.Node { + return nil +} + +// Collect gathers information for the structure described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// The field list of the receiver is populated +// by the same flattening algorithm employed by encoding/json. +// JSON struct tags are accounted for. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *StructInfo) Collect() *StructInfo { + if info == nil { + return nil + } + + type fieldData struct { + *StructField + + // Data for the encoding/json flattening algorithm. + nameFromTag bool + index []int + } + + info.once.Do(func() { + // Flattened list of fields with additional information. + fields := make([]fieldData, 0, info.typ.NumFields()) + + // Queued embedded types for current and next level. + current := make([]fieldData, 0, info.typ.NumFields()) + next := make([]fieldData, 1, max(1, info.typ.NumFields())) + + // Count of queued embedded types for current and next level. + count := make(map[*types.Struct]int) + nextCount := make(map[*types.Struct]int) + + // Set of visited types to avoid duplicating work. + visited := make(map[*types.Struct]bool) + + next[0] = fieldData{ + StructField: &StructField{ + Type: info.typ, + }, + } + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, count + clear(nextCount) + + for _, embedded := range current { + // Scan embedded type for fields to include. + estruct := embedded.Type.Underlying().(*types.Struct) + + // Skip previously visited structs + if visited[estruct] { + continue + } + visited[estruct] = true + + // WARNING: do not reuse cached info for embedded structs. + // It may lead to incorrect results for subtle reasons. + + for i := range estruct.NumFields() { + field := estruct.Field(i) + + // Retrieve type of field, following aliases conservatively + // and unwrapping exactly one pointer. + ftype := field.Type() + if ptr, ok := types.Unalias(ftype).(*types.Pointer); ok { + ftype = ptr.Elem() + } + + // Detect struct alias and keep it. + fstruct, _ := types.Unalias(ftype).(*types.Struct) + if fstruct == nil { + // Not a struct alias, follow alias chain. + ftype = types.Unalias(ftype) + fstruct, _ = ftype.Underlying().(*types.Struct) + } + + if field.Embedded() { + if !field.Exported() && fstruct == nil { + // Ignore embedded fields of unexported non-struct types. + continue + } + } else if !field.Exported() { + // Ignore unexported non-embedded fields. + continue + } + + // Retrieve and parse json tag. + tag := reflect.StructTag(estruct.Tag(i)).Get("json") + name, optional, quoted, visible := parseTag(tag) + if !visible { + // Ignored by encoding/json. + continue + } + + if !isValidFieldName(name) { + // Ignore alternative name if invalid. + name = "" + } + + index := make([]int, len(embedded.index)+1) + copy(index, embedded.index) + index[len(embedded.index)] = i + + if name != "" || !field.Embedded() || fstruct == nil { + // Tag name is non-empty, + // or field is not embedded, + // or field is not structure: + // add to field list. + + if !info.collector.options.UseInterfaces { + // In class mode, mark parametric fields as optional + // because there is no way to know their default JS value in advance. + if _, isTypeParam := types.Unalias(field.Type()).(*types.TypeParam); isTypeParam { + optional = true + } + } + + finfo := fieldData{ + StructField: &StructField{ + JsonName: name, + Type: field.Type(), + Optional: optional, + Quoted: quoted, + + Object: field, + }, + nameFromTag: name != "", + index: index, + } + + if name == "" { + finfo.JsonName = field.Name() + } + + fields = append(fields, finfo) + if count[estruct] > 1 { + // The struct we are scanning + // appears multiple times at the current level. + // This means that all its fields are ambiguous + // and must disappear. + // Duplicate them so that the field selection phase + // below will erase them. + fields = append(fields, finfo) + } + + continue + } + + // Queue embedded field for next level. + // If it has been queued already, do not duplicate it. + nextCount[fstruct]++ + if nextCount[fstruct] == 1 { + next = append(next, fieldData{ + StructField: &StructField{ + Type: ftype, + }, + index: index, + }) + } + } + } + } + + // Prepare for field selection phase. + slices.SortFunc(fields, func(f1 fieldData, f2 fieldData) int { + // Sort by name first. + if diff := strings.Compare(f1.JsonName, f2.JsonName); diff != 0 { + return diff + } + + // Break ties by depth of occurrence. + if diff := cmp.Compare(len(f1.index), len(f2.index)); diff != 0 { + return diff + } + + // Break ties by presence of json tag (prioritize presence). + if f1.nameFromTag != f2.nameFromTag { + if f1.nameFromTag { + return -1 + } else { + return 1 + } + } + + // Break ties by order of occurrence. + return slices.Compare(f1.index, f2.index) + }) + + fieldCount := 0 + + // Keep for each name the dominant field, drop those for which ties + // still exist (ignoring order of occurrence). + for i, j := 0, 1; j <= len(fields); j++ { + if j < len(fields) && fields[i].JsonName == fields[j].JsonName { + continue + } + + // If there is only one field with the current name, + // or there is a dominant one, keep it. + if i+1 == j || len(fields[i].index) != len(fields[i+1].index) || fields[i].nameFromTag != fields[i+1].nameFromTag { + fields[fieldCount] = fields[i] + fieldCount++ + } + + i = j + } + + fields = fields[:fieldCount] + + // Sort by order of occurrence. + slices.SortFunc(fields, func(f1 fieldData, f2 fieldData) int { + return slices.Compare(f1.index, f2.index) + }) + + // Copy selected fields to receiver. + info.Fields = make([]*StructField, len(fields)) + for i, field := range fields { + info.Fields[i] = field.StructField + } + + info.typ = nil + }) + + return info +} + +// parseTag parses a json field tag and extracts +// all options recognised by encoding/json. +func parseTag(tag string) (name string, optional bool, quoted bool, visible bool) { + if tag == "-" { + return "", false, false, false + } else { + visible = true + } + + parts := strings.Split(tag, ",") + + name = parts[0] + + for _, option := range parts[1:] { + switch option { + case "omitempty", "omitzero": + optional = true + case "string": + quoted = true + } + } + + return +} + +// isValidFieldName determines whether a field name is valid +// according to encoding/json. +func isValidFieldName(name string) bool { + if name == "" { + return false + } + + for _, c := range name { + if !strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c) && !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + + return true +} diff --git a/v3/internal/generator/collect/type.go b/v3/internal/generator/collect/type.go new file mode 100644 index 000000000..24e5adefc --- /dev/null +++ b/v3/internal/generator/collect/type.go @@ -0,0 +1,113 @@ +package collect + +import ( + "go/ast" + "go/types" + "slices" + "sync" +) + +// TypeInfo records information about a type declaration. +// +// Read accesses to any public field are only safe +// if a call to [TypeInfo.Collect] has completed before the access, +// for example by calling it in the accessing goroutine +// or before spawning the accessing goroutine. +type TypeInfo struct { + Name string + + // Alias is true for type aliases. + Alias bool + + Doc *ast.CommentGroup + Decl *GroupInfo + + obj *types.TypeName + node ast.Node + + collector *Collector + once sync.Once +} + +// newTypeInfo initialises a descriptor for the given named type object. +func newTypeInfo(collector *Collector, obj *types.TypeName) *TypeInfo { + return &TypeInfo{ + obj: obj, + collector: collector, + } +} + +// Type returns the unique TypeInfo instance +// associated to the given object within a collector. +// +// Type is safe for concurrent use. +func (collector *Collector) Type(obj *types.TypeName) *TypeInfo { + return collector.fromCache(obj).(*TypeInfo) +} + +func (info *TypeInfo) Object() types.Object { + return info.obj +} + +func (info *TypeInfo) Type() types.Type { + return info.obj.Type() +} + +func (info *TypeInfo) Node() ast.Node { + return info.Collect().node +} + +// Collect gathers information about the type described by its receiver. +// It can be called concurrently by multiple goroutines; +// the computation will be performed just once. +// +// Collect returns the receiver for chaining. +// It is safe to call Collect with nil receiver. +// +// After Collect returns, the calling goroutine and all goroutines +// it might spawn afterwards are free to access +// the receiver's fields indefinitely. +func (info *TypeInfo) Collect() *TypeInfo { + if info == nil { + return nil + } + + info.once.Do(func() { + collector := info.collector + + info.Name = info.obj.Name() + info.Alias = info.obj.IsAlias() + + path := collector.findDeclaration(info.obj) + if path == nil { + collector.logger.Warningf( + "package %s: type %s: could not find declaration for type object", + info.obj.Pkg().Path(), + info.Name, + ) + + // Provide dummy group. + info.Decl = newGroupInfo(nil).Collect() + return + } + + // path shape: *ast.TypeSpec, *ast.GenDecl, *ast.File + tspec := path[0].(*ast.TypeSpec) + + // Retrieve doc comments. + info.Doc = tspec.Doc + if info.Doc == nil { + info.Doc = tspec.Comment + } else if tspec.Comment != nil { + info.Doc = &ast.CommentGroup{ + List: slices.Concat(tspec.Doc.List, tspec.Comment.List), + } + } + + info.Decl = collector.fromCache(path[1]).(*GroupInfo).Collect() + + info.node = path[0] + }) + + return info +} diff --git a/v3/internal/generator/config/file.go b/v3/internal/generator/config/file.go new file mode 100644 index 000000000..1e3bc7477 --- /dev/null +++ b/v3/internal/generator/config/file.go @@ -0,0 +1,64 @@ +package config + +import ( + "io" + "os" + "path/filepath" +) + +// FileCreator abstracts away file and directory creation. +// We use this to implement tests cleanly. +// +// Paths are always relative to the output directory. +// +// A FileCreator must allow concurrent calls to Create transparently. +// Each [io.WriteCloser] instance returned by a call to Create +// will be used by one goroutine at a time; but distinct instances +// must support concurrent use by distinct goroutines. +type FileCreator interface { + Create(path string) (io.WriteCloser, error) +} + +// FileCreatorFunc is an adapter to allow +// the use of ordinary functions as file creators. +type FileCreatorFunc func(path string) (io.WriteCloser, error) + +// Create calls f(path). +func (f FileCreatorFunc) Create(path string) (io.WriteCloser, error) { + return f(path) +} + +// NullCreator is a dummy file creator implementation. +// Calls to Create never fail and return +// a writer that discards all incoming data. +var NullCreator FileCreator = FileCreatorFunc(func(path string) (io.WriteCloser, error) { + return nullWriteCloser{}, nil +}) + +// DirCreator returns a file creator that creates files +// relative to the given output directory. +// +// It joins the output directory and the file path, +// calls [os.MkdirAll] on the directory part of the result, +// then [os.Create] on the full file path. +func DirCreator(outputDir string) FileCreator { + return FileCreatorFunc(func(path string) (io.WriteCloser, error) { + path = filepath.Join(outputDir, path) + + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { + return nil, err + } + + return os.Create(path) + }) +} + +type nullWriteCloser struct{} + +func (nullWriteCloser) Write(data []byte) (int, error) { + return len(data), nil +} + +func (nullWriteCloser) Close() error { + return nil +} diff --git a/v3/internal/generator/config/log.go b/v3/internal/generator/config/log.go new file mode 100644 index 000000000..bbbd6c0db --- /dev/null +++ b/v3/internal/generator/config/log.go @@ -0,0 +1,100 @@ +package config + +import ( + "fmt" + + "github.com/pterm/pterm" +) + +// A Logger instance provides methods to format and report messages +// intended for the end user. +// +// All Logger methods may be called concurrently by its consumers. +type Logger interface { + // Errorf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as an error message. + Errorf(format string, a ...any) + + // Warningf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a warning message. + Warningf(format string, a ...any) + + // Infof should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as an informational message. + Infof(format string, a ...any) + + // Debugf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a debug message. + Debugf(format string, a ...any) + + // Statusf should process its arguments as if they were passed to fmt.Sprintf + // and report the resulting string to the user as a status message. + Statusf(format string, a ...any) +} + +// NullLogger is a dummy Logger implementation +// that discards all incoming messages. +var NullLogger Logger = nullLogger{} + +type nullLogger struct{} + +func (nullLogger) Errorf(format string, a ...any) {} +func (nullLogger) Warningf(format string, a ...any) {} +func (nullLogger) Infof(format string, a ...any) {} +func (nullLogger) Debugf(format string, a ...any) {} +func (nullLogger) Statusf(format string, a ...any) {} + +// DefaultPtermLogger returns a Logger implementation that writes +// to the default pterm printers for each logging level. +// +// If spinner is not nil, it is used to log status updates. +// The spinner must have been started already. +func DefaultPtermLogger(spinner *pterm.SpinnerPrinter) Logger { + return &PtermLogger{ + &pterm.Error, + &pterm.Warning, + &pterm.Info, + &pterm.Debug, + spinner, + } +} + +// PtermLogger is a Logger implementation that writes to pterm printers. +// If any field is nil, PtermLogger discards all messages of that level. +type PtermLogger struct { + Error pterm.TextPrinter + Warning pterm.TextPrinter + Info pterm.TextPrinter + Debug pterm.TextPrinter + Spinner *pterm.SpinnerPrinter +} + +func (logger *PtermLogger) Errorf(format string, a ...any) { + if logger.Error != nil { + logger.Error.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Warningf(format string, a ...any) { + if logger.Warning != nil { + logger.Warning.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Infof(format string, a ...any) { + if logger.Info != nil { + logger.Info.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Debugf(format string, a ...any) { + if logger.Debug != nil { + logger.Debug.Printfln(format, a...) + } +} + +func (logger *PtermLogger) Statusf(format string, a ...any) { + if logger.Spinner != nil { + logger.Spinner.UpdateText(fmt.Sprintf(format, a...)) + } +} diff --git a/v3/internal/generator/config/paths.go b/v3/internal/generator/config/paths.go new file mode 100644 index 000000000..84d18214c --- /dev/null +++ b/v3/internal/generator/config/paths.go @@ -0,0 +1,10 @@ +package config + +// WailsAppPkgPath is the official import path of Wails v3's application package. +const WailsAppPkgPath = "github.com/wailsapp/wails/v3/pkg/application" + +// SystemPaths holds resolved paths of required system packages. +type SystemPaths struct { + ContextPackage string + ApplicationPackage string +} diff --git a/v3/internal/generator/constants.go b/v3/internal/generator/constants.go new file mode 100644 index 000000000..3c0c85873 --- /dev/null +++ b/v3/internal/generator/constants.go @@ -0,0 +1,54 @@ +package generator + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" +) + +func GenerateConstants(goData []byte) (string, error) { + + // Create a new token file set and parser + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, "", goData, parser.AllErrors) + if err != nil { + return "", err + } + + // Extract constant declarations and generate JavaScript constants + var jsConstants []string + for _, decl := range f.Decls { + if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.CONST { + for _, spec := range gd.Specs { + if vs, ok := spec.(*ast.ValueSpec); ok { + for i, name := range vs.Names { + value := vs.Values[i] + if value != nil { + jsConstants = append(jsConstants, fmt.Sprintf("export const %s = %s;", name.Name, jsValue(value))) + } + } + } + } + } + } + + // Join the JavaScript constants into a single string + jsCode := strings.Join(jsConstants, "\n") + + return jsCode, nil +} + +func jsValue(expr ast.Expr) string { + // Implement conversion from Go constant value to JavaScript value here. + // You can add more cases for different types if needed. + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value + case *ast.Ident: + return e.Name + default: + return "" + } +} diff --git a/v3/internal/generator/constants_test.go b/v3/internal/generator/constants_test.go new file mode 100644 index 000000000..9e056dcac --- /dev/null +++ b/v3/internal/generator/constants_test.go @@ -0,0 +1,55 @@ +package generator + +import "testing" + +func TestGenerateConstants(t *testing.T) { + tests := []struct { + name string + goData []byte + want string + wantErr bool + }{ + { + name: "int", + goData: []byte(`package test +const one = 1`), + want: "export const one = 1;", + wantErr: false, + }, + { + name: "float", + goData: []byte(`package test +const one_point_five = 1.5`), + want: "export const one_point_five = 1.5;", + wantErr: false, + }, + { + name: "string", + goData: []byte(`package test +const one_as_a_string = "1"`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + { + name: "nested", + goData: []byte(`package test +const ( + one_as_a_string = "1" +)`), + want: `export const one_as_a_string = "1";`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateConstants(tt.goData) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateConstants() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GenerateConstants() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/v3/internal/generator/errors.go b/v3/internal/generator/errors.go new file mode 100644 index 000000000..cb000d1ad --- /dev/null +++ b/v3/internal/generator/errors.go @@ -0,0 +1,191 @@ +package generator + +import ( + "errors" + "fmt" + "maps" + "slices" + "sync" + + "github.com/wailsapp/wails/v3/internal/generator/config" +) + +// ErrNoContextPackage indicates that +// the canonical path for the standard context package +// did not match any actual package. +var ErrNoContextPackage = errors.New("standard context package not found at canonical import path ('context'): is the Wails v3 module properly installed? ") + +// ErrNoApplicationPackage indicates that +// the canonical path for the Wails application package +// did not match any actual package. +var ErrNoApplicationPackage = errors.New("Wails application package not found at canonical import path ('" + config.WailsAppPkgPath + "'): is the Wails v3 module properly installed? ") + +// ErrBadApplicationPackage indicates that +// the Wails application package has invalid content. +var ErrBadApplicationPackage = errors.New("package " + config.WailsAppPkgPath + ": function NewService has wrong signature: is the Wails v3 module properly installed? ") + +// ErrNoPackages is returned by [Generator.Generate] +// when [LoadPackages] returns no error and no packages. +var ErrNoPackages = errors.New("the given patterns matched no packages") + +// ErrorReport accumulates and logs error +// and warning messages, with deduplication. +// +// It implements the error interface; the Error method +// returns a report counting messages emitted so far. +// +// It also implements the interface [config.Logger] for convenience. +type ErrorReport struct { + logger config.Logger + + mu sync.Mutex + warnings map[string]bool + errors map[string]bool +} + +// NewErrorReport report initialises an ErrorReport instance +// with the provided Logger implementation. +// +// If logger is nil, messages will be accumulated but not logged. +func NewErrorReport(logger config.Logger) *ErrorReport { + if logger == nil { + logger = config.NullLogger + } + + return &ErrorReport{ + logger: logger, + warnings: make(map[string]bool), + errors: make(map[string]bool), + } +} + +// Error returns a string reporting the number +// of errors and warnings emitted so far. +func (report *ErrorReport) Error() string { + report.mu.Lock() + defer report.mu.Unlock() + + if len(report.errors) > 0 && len(report.warnings) == 0 { + var plural string + if len(report.errors) > 1 { + plural = "s" + } + return fmt.Sprintf("%d error%s emitted", len(report.errors), plural) + + } else if len(report.errors) == 0 && len(report.warnings) > 0 { + var plural string + if len(report.warnings) > 1 { + plural = "s" + } + + return fmt.Sprintf("%d warning%s emitted", len(report.warnings), plural) + + } else if len(report.errors) > 0 && len(report.warnings) > 0 { + var eplural, wplural string + if len(report.errors) > 1 { + eplural = "s" + } + if len(report.warnings) > 1 { + wplural = "s" + } + + return fmt.Sprintf("%d error%s and %d warning%s emitted", len(report.errors), eplural, len(report.warnings), wplural) + + } else { + return "no errors or warnings emitted" + } +} + +// HasErrors returns true if at least one error has been added to the report. +func (report *ErrorReport) HasErrors() bool { + report.mu.Lock() + result := len(report.errors) > 0 + report.mu.Unlock() + return result +} + +// HasWarnings returns true if at least one warning has been added to the report. +func (report *ErrorReport) HasWarnings() bool { + report.mu.Lock() + result := len(report.warnings) > 0 + report.mu.Unlock() + return result +} + +// Errors returns the list of error messages +// that have been added to the report. +// The order is randomised. +func (report *ErrorReport) Errors() []string { + report.mu.Lock() + defer report.mu.Unlock() + + return slices.Collect(maps.Keys(report.errors)) +} + +// Warnings returns the list of warning messages +// that have been added to the report. +// The order is randomised. +func (report *ErrorReport) Warnings() []string { + report.mu.Lock() + defer report.mu.Unlock() + + return slices.Collect(maps.Keys(report.warnings)) +} + +// Errorf formats an error message and adds it to the report. +// If not already present, the message is forwarded +// to the logger instance provided during initialisation. +func (report *ErrorReport) Errorf(format string, a ...any) { + msg := fmt.Sprintf(format, a...) + + report.mu.Lock() + defer report.mu.Unlock() + + present := report.errors[msg] + report.errors[msg] = true + + if !present { + report.logger.Errorf(format, a...) + } +} + +// Warningf formats an error message and adds it to the report. +// If not already present, the message is forwarded +// to the logger instance provided during initialisation. +func (report *ErrorReport) Warningf(format string, a ...any) { + msg := fmt.Sprintf(format, a...) + + report.mu.Lock() + defer report.mu.Unlock() + + present := report.warnings[msg] + report.warnings[msg] = true + + if !present { + report.logger.Warningf(format, a...) + } +} + +// Infof forwards the given informational message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Infof(format string, a ...any) { + report.logger.Infof(format, a...) +} + +// Debugf forwards the given informational message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Debugf(format string, a ...any) { + report.logger.Debugf(format, a...) +} + +// Statusf forwards the given status message +// to the logger instance provided during initialisation. +// +// This method is here just for convenience and performs no deduplication. +func (report *ErrorReport) Statusf(format string, a ...any) { + report.logger.Statusf(format, a...) +} diff --git a/v3/internal/generator/generate.go b/v3/internal/generator/generate.go new file mode 100644 index 000000000..a3162ea8a --- /dev/null +++ b/v3/internal/generator/generate.go @@ -0,0 +1,262 @@ +package generator + +import ( + "fmt" + "io" + "strings" + "sync" + "time" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" + "github.com/wailsapp/wails/v3/internal/generator/config" + "github.com/wailsapp/wails/v3/internal/generator/render" +) + +// Generator wraps all bookkeeping data structures that are needed +// to generate bindings for a set of packages. +type Generator struct { + options *flags.GenerateBindingsOptions + creator config.FileCreator + + // serviceFiles maps service file paths to their type object. + // It is used for lower/upper-case collision detection. + // Keys are strings, values are *types.TypeName. + serviceFiles sync.Map + + collector *collect.Collector + renderer *render.Renderer + + logger *ErrorReport + scheduler scheduler +} + +// NewGenerator configures a new generator instance. +// The options argument must not be nil. +// If creator is nil, no output file will be created. +// If logger is not nil, it is used to report messages interactively. +func NewGenerator(options *flags.GenerateBindingsOptions, creator config.FileCreator, logger config.Logger) *Generator { + if creator == nil { + creator = config.NullCreator + } + + report := NewErrorReport(logger) + + return &Generator{ + options: options, + creator: config.FileCreatorFunc(func(path string) (io.WriteCloser, error) { + report.Debugf("writing output file %s", path) + return creator.Create(path) + }), + + logger: report, + } +} + +// Generate runs the binding generation process +// for the packages specified by the given patterns. +// +// Concurrent or repeated calls to Generate with the same receiver +// are not allowed. +// +// The stats result field is never nil. +// +// The error result field is nil in case of complete success (no warning). +// Otherwise, it may either report errors that occured while loading +// the initial set of packages, or errors returned by the static analyser, +// or be an [ErrorReport] instance. +// +// If error is an ErrorReport, it may have accumulated no errors, just warnings. +// When this is the case, all bindings have been generated successfully. +// +// Parsing/type-checking errors or errors encountered while writing +// individual files will be printed directly to the [config.Logger] instance +// provided during initialisation. +func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats, err error) { + stats = &collect.Stats{} + stats.Start() + defer stats.Stop() + + // Validate file names. + err = generator.validateFileNames() + if err != nil { + return + } + + // Parse build flags. + buildFlags, err := generator.options.BuildFlags() + if err != nil { + return + } + + // Start package loading feedback. + var lpkgMutex sync.Mutex + generator.logger.Statusf("Loading packages...") + go func() { + time.Sleep(5 * time.Second) + if lpkgMutex.TryLock() { + generator.logger.Statusf("Loading packages... (this may take a long time)") + lpkgMutex.Unlock() + } + }() + + systemPaths, err := ResolveSystemPaths(buildFlags) + if err != nil { + return + } + + // Load initial packages. + pkgs, err := LoadPackages(buildFlags, patterns...) + + // Suppress package loading feedback. + lpkgMutex.Lock() + + // Check for loading errors. + if err != nil { + return + } + if len(patterns) > 0 && len(pkgs) == 0 { + err = ErrNoPackages + return + } + + // Report parsing/type-checking errors. + for _, pkg := range pkgs { + for _, err := range pkg.Errors { + generator.logger.Warningf("%v", err) + } + } + + // Panic on repeated calls. + if generator.collector != nil { + panic("Generate() must not be called more than once on the same receiver") + } + + // Initialise subcomponents. + generator.collector = collect.NewCollector(pkgs, systemPaths, generator.options, &generator.scheduler, generator.logger) + generator.renderer = render.NewRenderer(generator.options, generator.collector) + + // Update status. + generator.logger.Statusf("Looking for services...") + serviceFound := sync.OnceFunc(func() { generator.logger.Statusf("Generating service bindings...") }) + + // Run static analysis. + services, err := FindServices(pkgs, systemPaths, generator.logger) + + // Check for analyser errors. + if err != nil { + return + } + + // Discard unneeded data. + pkgs = nil + + // Schedule code generation for each found service. + for obj := range services { + serviceFound() + generator.scheduler.Schedule(func() { + generator.generateService(obj) + }) + } + + // Wait until all services have been generated and all models collected. + generator.scheduler.Wait() + + // Invariants: + // - Service files have been generated for all discovered services; + // - ModelInfo.Collect has been called on all discovered models, and therefore + // - all required models have been discovered. + + // Update status. + if generator.options.NoIndex { + generator.logger.Statusf("Generating models...") + } else { + generator.logger.Statusf("Generating models and index files...") + } + + // Schedule models, index and included files generation for each package. + for info := range generator.collector.Iterate { + generator.scheduler.Schedule(func() { + generator.generateModelsIndexIncludes(info) + }) + } + + // Wait until all models and indices have been generated. + generator.scheduler.Wait() + + // Populate stats. + generator.logger.Statusf("Collecting stats...") + for info := range generator.collector.Iterate { + stats.Add(info.Stats()) + } + + // Return non-empty error report. + if generator.logger.HasErrors() || generator.logger.HasWarnings() { + err = generator.logger + } + + return +} + +// generateModelsIndexIncludes schedules generation of public/private model files, +// included files and, if allowed by the options, +// of an index file for the given package. +func (generator *Generator) generateModelsIndexIncludes(info *collect.PackageInfo) { + index := info.Index(generator.options.TS) + + // info.Index implies info.Collect: goroutines spawned below + // can access package information freely. + + if len(index.Models) > 0 { + generator.scheduler.Schedule(func() { + generator.generateModels(info, index.Models) + }) + } + + if len(index.Package.Includes) > 0 { + generator.scheduler.Schedule(func() { + generator.generateIncludes(index) + }) + } + + if !generator.options.NoIndex && !index.IsEmpty() { + generator.generateIndex(index) + } +} + +// validateFileNames validates user-provided filenames. +func (generator *Generator) validateFileNames() error { + switch { + case generator.options.ModelsFilename == "": + return fmt.Errorf("models filename must not be empty") + + case !generator.options.NoIndex && generator.options.IndexFilename == "": + return fmt.Errorf("package index filename must not be empty") + + case generator.options.ModelsFilename != strings.ToLower(generator.options.ModelsFilename): + return fmt.Errorf("models filename must not contain uppercase characters") + + case generator.options.IndexFilename != strings.ToLower(generator.options.IndexFilename): + return fmt.Errorf("package index filename must not contain uppercase characters") + + case !generator.options.NoIndex && generator.options.ModelsFilename == generator.options.IndexFilename: + return fmt.Errorf("models and package indexes cannot share the same filename") + } + + return nil +} + +// scheduler provides an implementation of the [collect.Scheduler] interface. +type scheduler struct { + sync.WaitGroup +} + +// Schedule runs the given function concurrently, +// registering it on the scheduler's wait group. +func (sched *scheduler) Schedule(task func()) { + sched.Add(1) + go func() { + defer sched.Done() + task() + }() +} diff --git a/v3/internal/generator/generate_test.go b/v3/internal/generator/generate_test.go new file mode 100644 index 000000000..0e05cb6cb --- /dev/null +++ b/v3/internal/generator/generate_test.go @@ -0,0 +1,241 @@ +package generator + +import ( + "errors" + "fmt" + "github.com/wailsapp/wails/v3/internal/generator/render" + "io" + "io/fs" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/config" +) + +const testcases = "github.com/wailsapp/wails/v3/internal/generator/testcases/..." + +type testParams struct { + name string + options *flags.GenerateBindingsOptions + outputDir string + want map[string]bool +} + +func TestGenerator(t *testing.T) { + const ( + useNamesBit = 1 << iota + useInterfacesBit + tsBit + ) + + // Generate configuration matrix. + tests := make([]*testParams, 1<<3) + for i := range tests { + options := &flags.GenerateBindingsOptions{ + ModelsFilename: "models", + IndexFilename: "index", + + UseBundledRuntime: true, + + TS: i&tsBit != 0, + UseInterfaces: i&useInterfacesBit != 0, + UseNames: i&useNamesBit != 0, + } + + name := configString(options) + + tests[i] = &testParams{ + name: name, + options: options, + outputDir: filepath.Join("testdata/output", name), + want: make(map[string]bool), + } + } + + for _, test := range tests { + // Create output dir. + if err := os.MkdirAll(test.outputDir, 0777); err != nil { + t.Fatal(err) + } + + // Walk output dir. + err := filepath.WalkDir(test.outputDir, func(path string, d fs.DirEntry, err error) error { + // Skip directories. + if d.IsDir() { + return nil + } + + // Skip got files. + if strings.HasSuffix(d.Name(), ".got.js") || strings.HasSuffix(d.Name(), ".got.ts") { + return nil + } + + // Record file. + test.want[filepath.Clean("."+path[len(test.outputDir):])] = false + return nil + }) + + if err != nil { + t.Fatal(err) + } + } + + // Run tests. + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + creator := outputCreator(t, test) + + generator := NewGenerator( + test.options, + creator, + config.DefaultPtermLogger(nil), + ) + + _, err := generator.Generate(testcases) + if report := (*ErrorReport)(nil); errors.As(err, &report) { + if report.HasErrors() { + t.Error(report) + } else if report.HasWarnings() { + pterm.Warning.Println(report) + } + + // Log warnings and compare with reference output. + if log, err := creator.Create("warnings.log"); err != nil { + t.Error(err) + } else { + func() { + defer log.Close() + + warnings := report.Warnings() + slices.Sort(warnings) + + for _, msg := range warnings { + fmt.Fprint(log, msg, render.Newline) + } + }() + } + } else if err != nil { + t.Error(err) + } + + for path, present := range test.want { + if !present { + t.Errorf("Missing output file '%s'", path) + } + } + }) + } +} + +// configString computes a subtest name from the given configuration. +func configString(options *flags.GenerateBindingsOptions) string { + lang := "JS" + if options.TS { + lang = "TS" + } + return fmt.Sprintf("lang=%s/UseInterfaces=%v/UseNames=%v", lang, options.UseInterfaces, options.UseNames) +} + +// outputCreator returns a FileCreator that detects want/got pairs +// and schedules them for comparison. +// +// If no corresponding want file exists, it is created and reported. +func outputCreator(t *testing.T, params *testParams) config.FileCreator { + var mu sync.Mutex + return config.FileCreatorFunc(func(path string) (io.WriteCloser, error) { + path = filepath.Clean(path) + prefixedPath := filepath.Join(params.outputDir, path) + + // Protect want map accesses. + mu.Lock() + defer mu.Unlock() + + if seen, ok := params.want[path]; ok { + // File exists: mark as seen and compare. + if seen { + t.Errorf("Duplicate output file '%s'", path) + } + params.want[path] = true + + // Open want file. + wf, err := os.Open(prefixedPath) + if err != nil { + return nil, err + } + + // Create or truncate got file. + ext := filepath.Ext(prefixedPath) + gf, err := os.Create(fmt.Sprintf("%s.got%s", prefixedPath[:len(prefixedPath)-len(ext)], ext)) + if err != nil { + return nil, err + } + + // Initialise comparer. + return &outputComparer{t, path, wf, gf}, nil + } else { + // File does not exist: create it. + t.Errorf("Unexpected output file '%s'", path) + params.want[path] = true + + if err := os.MkdirAll(filepath.Dir(prefixedPath), 0777); err != nil { + return nil, err + } + + return os.Create(prefixedPath) + } + }) +} + +// outputComparer is a io.WriteCloser that writes to got. +// +// When Close is called, it compares want to got; if they are identical, +// it deletes got; otherwise it reports a testing error. +type outputComparer struct { + t *testing.T + path string + want *os.File + got *os.File +} + +func (comparer *outputComparer) Write(data []byte) (int, error) { + return comparer.got.Write(data) +} + +func (comparer *outputComparer) Close() error { + defer comparer.want.Close() + defer comparer.got.Close() + + comparer.got.Seek(0, io.SeekStart) + + // Read want data. + want, err := io.ReadAll(comparer.want) + if err != nil { + comparer.t.Error(err) + return nil + } + + got, err := io.ReadAll(comparer.got) + if err != nil { + comparer.t.Error(err) + return nil + } + + if diff := cmp.Diff(want, got); diff != "" { + comparer.t.Errorf("Output file '%s' mismatch (-want +got):\n%s", comparer.path, diff) + } else { + // On success, delete got file. + comparer.got.Close() + if err := os.Remove(comparer.got.Name()); err != nil { + comparer.t.Error(err) + } + } + + return nil +} diff --git a/v3/internal/generator/includes.go b/v3/internal/generator/includes.go new file mode 100644 index 000000000..b8bed2392 --- /dev/null +++ b/v3/internal/generator/includes.go @@ -0,0 +1,100 @@ +package generator + +import ( + "io" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateIncludes copies included files to the package directory +// for the package summarised by the given index. +func (generator *Generator) generateIncludes(index *collect.PackageIndex) { + for name, path := range index.Package.Includes { + // Validate filename. + switch name { + case generator.renderer.ModelsFile(): + if index.HasExportedModels { + generator.logger.Errorf( + "package %s: included file '%s' collides with models filename; please rename the file or choose a different filename for models", + index.Package.Path, + path, + ) + return + } + + case generator.renderer.IndexFile(): + if !generator.options.NoIndex && !index.IsEmpty() { + generator.logger.Errorf( + "package %s: included file '%s' collides with JS/TS index filename; please rename the file or choose a different filename for JS/TS indexes", + index.Package.Path, + path, + ) + return + } + } + + // Validate against services. + service, ok := slices.BinarySearchFunc(index.Services, name, func(service *collect.ServiceInfo, name string) int { + return strings.Compare(generator.renderer.ServiceFile(service.Name), name) + }) + if ok { + generator.logger.Errorf( + "package %s: included file '%s' collides with filename for service %s; please rename either the file or the service", + index.Package.Path, + path, + index.Services[service].Name, + ) + return + } + + // Copy file to destination in separate goroutine. + generator.scheduler.Schedule(func() { + src, err := os.Open(path) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not read included file '%s'", index.Package.Path, path) + return + } + defer src.Close() + + stat, err := src.Stat() + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not read included file '%s'", index.Package.Path, path) + return + } + + if stat.IsDir() { + generator.logger.Errorf( + "package %s: included file '%s' is a directory; please glob or list all descendants explicitly", + index.Package.Path, + path, + ) + return + } + + dst, err := generator.creator.Create(filepath.Join(index.Package.Path, name)) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not write included file '%s'", index.Package.Path, name) + return + } + defer func() { + if err := dst.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not write included file '%s'", index.Package.Path, name) + } + }() + + _, err = io.Copy(dst, src) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: could not copy included file '%s'", index.Package.Path, name) + } + }) + } +} diff --git a/v3/internal/generator/index.go b/v3/internal/generator/index.go new file mode 100644 index 000000000..e7a73c71f --- /dev/null +++ b/v3/internal/generator/index.go @@ -0,0 +1,53 @@ +package generator + +import ( + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateIndex generates an index file from the given index information. +func (generator *Generator) generateIndex(index *collect.PackageIndex) { + defer generator.reportDualRoles(index) + + file, err := generator.creator.Create(filepath.Join(index.Package.Path, generator.renderer.IndexFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + } + }() + + err = generator.renderer.Index(file, index) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: index generation failed", index.Package.Path) + } +} + +// reportDualRoles checks for models types that are also service types +// and emits a warning. +func (generator *Generator) reportDualRoles(index *collect.PackageIndex) { + services, models := index.Services, index.Models + for len(services) > 0 && len(models) > 0 { + if services[0].Name < models[0].Name { + services = services[1:] + } else if services[0].Name > models[0].Name { + models = models[1:] + } else { + generator.logger.Warningf( + "package %s: type %s has been marked both as a service and as a model; shadowing between the two may take place when importing generated JS indexes", + index.Package.Path, + services[0].Name, + ) + + services = services[1:] + models = models[1:] + } + } +} diff --git a/v3/internal/generator/load.go b/v3/internal/generator/load.go new file mode 100644 index 000000000..a0d6f5f6b --- /dev/null +++ b/v3/internal/generator/load.go @@ -0,0 +1,110 @@ +package generator + +import ( + "go/ast" + "go/parser" + "go/token" + + "github.com/wailsapp/wails/v3/internal/generator/config" + "golang.org/x/tools/go/packages" +) + +// ResolveSystemPaths resolves paths for the Wails system +func ResolveSystemPaths(buildFlags []string) (paths *config.SystemPaths, err error) { + // Resolve context pkg path. + contextPkgPaths, err := ResolvePatterns(buildFlags, "context") + if err != nil { + return + } else if len(contextPkgPaths) < 1 { + err = ErrNoContextPackage + return + } else if len(contextPkgPaths) > 1 { + // This should never happen... + panic("context package path matched multiple packages") + } + + // Resolve wails app pkg path. + wailsAppPkgPaths, err := ResolvePatterns(buildFlags, config.WailsAppPkgPath) + if err != nil { + return + } else if len(wailsAppPkgPaths) < 1 { + err = ErrNoApplicationPackage + return + } else if len(wailsAppPkgPaths) > 1 { + // This should never happen... + panic("wails application package path matched multiple packages") + } + + paths = &config.SystemPaths{ + ContextPackage: contextPkgPaths[0], + ApplicationPackage: wailsAppPkgPaths[0], + } + return +} + +// ResolvePatterns returns a slice containing all package paths +// that match the given patterns, according to the underlying build tool +// and within the context of the current working directory. +func ResolvePatterns(buildFlags []string, patterns ...string) (paths []string, err error) { + rewrittenPatterns := make([]string, len(patterns)) + for i, pattern := range patterns { + rewrittenPatterns[i] = "pattern=" + pattern + } + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName, + BuildFlags: buildFlags, + }, rewrittenPatterns...) + + for _, pkg := range pkgs { + paths = append(paths, pkg.PkgPath) + } + + return +} + +// LoadPackages loads the packages specified by the given patterns +// and their whole dependency tree. It returns a slice containing +// all packages that match the given patterns and all of their direct +// and indirect dependencies. +// +// The returned slice is in post-order w.r.t. the dependency relation, +// i.e. if package A depends on package B, then package B precedes package A. +// +// All returned package instances include syntax trees and full type information. +// +// Syntax is loaded in the context of a global [token.FileSet], +// which is available through the field [packages.Package.Fset] +// on each returned package. Therefore, source positions +// are canonical across all loaded packages. +func LoadPackages(buildFlags []string, patterns ...string) (pkgs []*packages.Package, err error) { + rewrittenPatterns := make([]string, len(patterns)) + for i, pattern := range patterns { + rewrittenPatterns[i] = "pattern=" + pattern + } + + // Global file set. + fset := token.NewFileSet() + + roots, err := packages.Load(&packages.Config{ + // NOTE: some Go maintainers now believe deprecation was an error and recommend using Load* modes + // (see e.g. https://github.com/golang/go/issues/48226#issuecomment-1948792315). + Mode: packages.LoadAllSyntax, + BuildFlags: buildFlags, + Fset: fset, + ParseFile: func(fset *token.FileSet, filename string, src []byte) (file *ast.File, err error) { + file, err = parser.ParseFile(fset, filename, src, parser.ParseComments|parser.SkipObjectResolution) + return + }, + }, rewrittenPatterns...) + + // Flatten dependency tree. + packages.Visit(roots, nil, func(pkg *packages.Package) { + if pkg.Fset != fset { + panic("fileset missing or not the global one") + } + pkgs = append(pkgs, pkg) + }) + + return +} diff --git a/v3/internal/generator/models.go b/v3/internal/generator/models.go new file mode 100644 index 000000000..2fe4f407f --- /dev/null +++ b/v3/internal/generator/models.go @@ -0,0 +1,39 @@ +package generator + +import ( + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// generateModels generates a JS/TS models file for the given list of models. +// A call to info.Collect must complete before entering generateModels. +func (generator *Generator) generateModels(info *collect.PackageInfo, models []*collect.ModelInfo) { + // Merge all import maps. + imports := collect.NewImportMap(info) + for _, model := range models { + imports.Merge(model.Imports) + } + + // Clear irrelevant imports. + imports.ImportModels = false + + file, err := generator.creator.Create(filepath.Join(info.Path, generator.renderer.ModelsFile())) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + } + }() + + err = generator.renderer.Models(file, imports, models) + if err != nil { + generator.logger.Errorf("%v", err) + generator.logger.Errorf("package %s: models generation failed", info.Path) + } +} diff --git a/v3/internal/generator/render/create.go b/v3/internal/generator/render/create.go new file mode 100644 index 000000000..1332345d1 --- /dev/null +++ b/v3/internal/generator/render/create.go @@ -0,0 +1,391 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" + "golang.org/x/tools/go/types/typeutil" +) + +// SkipCreate returns true if the given array of types needs no creation code. +func (m *module) SkipCreate(ts []types.Type) bool { + for _, typ := range ts { + if m.NeedsCreate(typ) { + return false + } + } + return true +} + +// NeedsCreate returns true if the given type needs some creation code. +func (m *module) NeedsCreate(typ types.Type) bool { + return m.needsCreateImpl(typ, new(typeutil.Map)) +} + +// needsCreateImpl provides the actual implementation of NeedsCreate. +// The visited parameter is used to break cycles. +func (m *module) needsCreateImpl(typ types.Type, visited *typeutil.Map) bool { + switch t := typ.(type) { + case *types.Alias: + return m.needsCreateImpl(types.Unalias(typ), visited) + + case *types.Named: + if visited.Set(typ, true) != nil { + // The only way to hit a cycle here + // is through a chain of structs, nested pointers and arrays (not slices). + // We can safely return false at this point + // as the final answer is independent of the cycle. + return false + } + + if t.Obj().Pkg() == nil { + // Builtin named type: render underlying type. + return m.needsCreateImpl(t.Underlying(), visited) + } + + if collect.IsAny(typ) || collect.IsStringAlias(typ) { + break + } else if collect.IsClass(typ) { + return true + } else { + return m.needsCreateImpl(t.Underlying(), visited) + } + + case *types.Array, *types.Pointer: + return m.needsCreateImpl(typ.(interface{ Elem() types.Type }).Elem(), visited) + + case *types.Map, *types.Slice: + return true + + case *types.Struct: + if t.NumFields() == 0 || collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler || collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return false + } + + info := m.collector.Struct(t) + info.Collect() + + for _, field := range info.Fields { + if m.needsCreateImpl(field.Type, visited) { + return true + } + } + + case *types.TypeParam: + return true + } + + return false +} + +// JSCreate renders JS/TS code that creates an instance +// of the given type from JSON data. +// +// JSCreate's output may be incorrect +// if m.Imports.AddType has not been called for the given type. +func (m *module) JSCreate(typ types.Type) string { + return m.JSCreateWithParams(typ, "") +} + +// JSCreateWithParams renders JS/TS code that creates an instance +// of the given type from JSON data. For generic types, +// it renders parameterised code. +// +// JSCreateWithParams's output may be incorrect +// if m.Imports.AddType has not been called for the given type. +func (m *module) JSCreateWithParams(typ types.Type, params string) string { + if len(params) > 0 && !m.hasTypeParams(typ) { + // Forget params for non-generic types. + params = "" + } + + switch t := typ.(type) { + case *types.Alias: + return m.JSCreateWithParams(types.Unalias(typ), params) + + case *types.Array, *types.Pointer: + pp, ok := m.postponedCreates.At(typ).(*postponed) + if ok { + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + createElement := m.JSCreateWithParams(typ.(interface{ Elem() types.Type }).Elem(), params) + if createElement != "$Create.Any" { + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + case *types.Map: + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + m.JSCreateWithParams(t.Elem(), params) + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Named: + if t.Obj().Pkg() == nil { + // Builtin named type: render underlying type. + return m.JSCreateWithParams(t.Underlying(), params) + } + + if !m.NeedsCreate(typ) { + break + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 { + // Postpone type args. + for i := range t.TypeArgs().Len() { + m.JSCreateWithParams(t.TypeArgs().At(i), params) + } + } + + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + + if !collect.IsClass(typ) { + m.JSCreateWithParams(t.Underlying(), params) + } + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Slice: + if types.Identical(typ, typeByteSlice) { + return "$Create.ByteSlice" + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if !ok { + m.JSCreateWithParams(t.Elem(), params) + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + } + + return fmt.Sprintf("$$createType%d%s", pp.index, params) + + case *types.Struct: + if t.NumFields() == 0 || collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler || collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + break + } + + pp, ok := m.postponedCreates.At(typ).(*postponed) + if ok { + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + info := m.collector.Struct(t) + info.Collect() + + postpone := false + for _, field := range info.Fields { + if m.JSCreateWithParams(field.Type, params) != "$Create.Any" { + postpone = true + } + } + + if postpone { + pp = &postponed{m.postponedCreates.Len(), params} + m.postponedCreates.Set(typ, pp) + return fmt.Sprintf("$$createType%d%s", pp.index, params) + } + + case *types.TypeParam: + return fmt.Sprintf("$$createParam%s", typeparam(t.Index(), t.Obj().Name())) + } + + return "$Create.Any" +} + +// PostponedCreates returns the list of postponed create functions +// for the given module. +func (m *module) PostponedCreates() []string { + result := make([]string, m.postponedCreates.Len()) + + m.postponedCreates.Iterate(func(key types.Type, value any) { + pp := value.(*postponed) + + pre, post := "", "" + if pp.params != "" { + if m.TS { + pre = createParamRegex.ReplaceAllString(pp.params, "${0}: any") + " => " + } else { + pre = "/** @type {(...args: any[]) => any} */(" + pp.params + " => " + post = ")" + } + } + + switch t := key.(type) { + case *types.Array, *types.Slice: + result[pp.index] = fmt.Sprintf("%s$Create.Array(%s)%s", pre, m.JSCreateWithParams(t.(interface{ Elem() types.Type }).Elem(), pp.params), post) + + case *types.Map: + result[pp.index] = fmt.Sprintf("%s$Create.Map($Create.Any, %s)%s", pre, m.JSCreateWithParams(t.Elem(), pp.params), post) + + case *types.Named: + if !collect.IsClass(key) { + // Creation functions for non-struct named types + // require an indirect assignment to break cycles. + + // Typescript cannot infer the return type on its own: add hints. + cast, argType, returnType := "", "", "" + if m.TS { + argType = ": any[]" + returnType = ": any" + } else { + cast = "/** @type {(...args: any[]) => any} */" + } + + result[pp.index] = fmt.Sprintf(` +%s(function $$initCreateType%d(...args%s)%s { + if ($$createType%d === $$initCreateType%d) { + $$createType%d = %s%s%s; + } + return $$createType%d(...args); +})`, + cast, pp.index, argType, returnType, + pp.index, pp.index, + pp.index, pre, m.JSCreateWithParams(t.Underlying(), pp.params), post, + pp.index, + )[1:] // Remove initial newline. + + // We're done. + break + } + + var builder strings.Builder + + builder.WriteString(pre) + + if t.Obj().Pkg().Path() == m.Imports.Self { + if m.Imports.ImportModels { + builder.WriteString("$models.") + } + } else { + builder.WriteString(jsimport(m.Imports.External[t.Obj().Pkg().Path()])) + builder.WriteRune('.') + } + builder.WriteString(jsid(t.Obj().Name())) + builder.WriteString(".createFrom") + + if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 { + builder.WriteString("(") + for i := range t.TypeArgs().Len() { + if i > 0 { + builder.WriteString(", ") + } + builder.WriteString(m.JSCreateWithParams(t.TypeArgs().At(i), pp.params)) + } + builder.WriteString(")") + } + builder.WriteString(post) + + result[pp.index] = builder.String() + + case *types.Pointer: + result[pp.index] = fmt.Sprintf("%s$Create.Nullable(%s)%s", pre, m.JSCreateWithParams(t.Elem(), pp.params), post) + + case *types.Struct: + info := m.collector.Struct(t) + info.Collect() + + var builder strings.Builder + builder.WriteString(pre) + builder.WriteString("$Create.Struct({") + + for _, field := range info.Fields { + createField := m.JSCreateWithParams(field.Type, pp.params) + if createField == "$Create.Any" { + continue + } + + builder.WriteString("\n \"") + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteString("\": ") + builder.WriteString(createField) + builder.WriteRune(',') + } + + if len(info.Fields) > 0 { + builder.WriteRune('\n') + } + builder.WriteString("})") + builder.WriteString(post) + + result[pp.index] = builder.String() + + default: + result[pp.index] = pre + "$Create.Any" + post + } + }) + + if Newline != "\n" { + // Replace newlines according to local git config. + for i := range result { + result[i] = strings.ReplaceAll(result[i], "\n", Newline) + } + } + + return result +} + +type postponed struct { + index int + params string +} + +// hasTypeParams returns true if the given type depends upon type parameters. +func (m *module) hasTypeParams(typ types.Type) bool { + switch t := typ.(type) { + case *types.Alias: + if t.Obj().Pkg() == nil { + // Builtin alias: these are never rendered as templates. + return false + } + + return m.hasTypeParams(types.Unalias(typ)) + + case *types.Array, *types.Pointer, *types.Slice: + return m.hasTypeParams(typ.(interface{ Elem() types.Type }).Elem()) + + case *types.Map: + return m.hasTypeParams(t.Key()) || m.hasTypeParams(t.Elem()) + + case *types.Named: + if t.Obj().Pkg() == nil { + // Builtin named type: these are never rendered as templates. + return false + } + + if targs := t.TypeArgs(); targs != nil { + for i := range targs.Len() { + if m.hasTypeParams(targs.At(i)) { + return true + } + } + } + + case *types.Struct: + info := m.collector.Struct(t) + info.Collect() + + for _, field := range info.Fields { + if m.hasTypeParams(field.Type) { + return true + } + } + + case *types.TypeParam: + return true + } + + return false +} diff --git a/v3/internal/generator/render/default.go b/v3/internal/generator/render/default.go new file mode 100644 index 000000000..793c704ba --- /dev/null +++ b/v3/internal/generator/render/default.go @@ -0,0 +1,179 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// JSDefault renders the Javascript representation +// of the zero value of the given type, +// using the receiver's import map to resolve dependencies. +// +// JSDefault's output may be incorrect +// if imports.AddType has not been called for the given type. +func (m *module) JSDefault(typ types.Type, quoted bool) (result string) { + switch t := typ.(type) { + case *types.Alias, *types.Named: + result, ok := m.renderNamedDefault(t.(aliasOrNamed), quoted) + if ok { + return result + } + + case *types.Array: + if t.Len() == 0 { + return "[]" + } else { + // Initialise array with expected number of elements + return fmt.Sprintf("Array.from({ length: %d }, () => %s)", t.Len(), m.JSDefault(t.Elem(), false)) + } + + case *types.Slice: + if types.Identical(typ, typeByteSlice) { + return `""` + } else { + return "[]" + } + + case *types.Basic: + return m.renderBasicDefault(t, quoted) + + case *types.Map: + return "{}" + + case *types.Struct: + return m.renderStructDefault(t) + + case *types.TypeParam: + // Should be unreachable + panic("type parameters have no default value") + } + + // Fall back to null. + // encoding/json ignores null values so this is safe. + return "null" +} + +// renderBasicDefault outputs the Javascript representation +// of the zero value for the given basic type. +func (*module) renderBasicDefault(typ *types.Basic, quoted bool) string { + switch { + case typ.Info()&types.IsBoolean != 0: + if quoted { + return `"false"` + } else { + return "false" + } + + case typ.Info()&types.IsNumeric != 0 && typ.Info()&types.IsComplex == 0: + if quoted { + return `"0"` + } else { + return "0" + } + + case typ.Info()&types.IsString != 0: + if quoted { + return `'""'` + } else { + return `""` + } + } + + // Fall back to untyped mode. + if quoted { + return `""` + } else { + // encoding/json ignores null values so this is safe. + return "null" + } +} + +// renderNamedDefault outputs the Javascript representation +// of the zero value for the given alias or named type. +// The result field named 'ok' is true when the resulting code is valid. +// If false, it must be discarded. +func (m *module) renderNamedDefault(typ aliasOrNamed, quoted bool) (result string, ok bool) { + if typ.Obj().Pkg() == nil { + // Builtin alias or named type: render underlying type. + return m.JSDefault(typ.Underlying(), quoted), true + } + + if quoted { + // WARN: Do not test with IsAny/IsStringAlias here!! We only want to catch marshalers. + if collect.MaybeJSONMarshaler(typ) == collect.NonMarshaler && collect.MaybeTextMarshaler(typ) == collect.NonMarshaler { + if basic, ok := typ.Underlying().(*types.Basic); ok { + // Quoted mode for basic alias/named type that is not a marshaler: delegate. + return m.renderBasicDefault(basic, quoted), true + } + // No need to handle typeparams: they are initialised to null anyways. + } + } + + prefix := "" + if m.Imports.ImportModels { + prefix = "$models." + } + + if collect.IsAny(typ) { + return "", false + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return `""`, true + } else if collect.IsClass(typ) && !istpalias(typ) { + if typ.Obj().Pkg().Path() == m.Imports.Self { + return fmt.Sprintf("(new %s%s())", prefix, jsid(typ.Obj().Name())), true + } else { + return fmt.Sprintf("(new %s.%s())", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true + } + } else if _, isAlias := typ.(*types.Alias); isAlias { + return m.JSDefault(types.Unalias(typ), quoted), true + } else if len(m.collector.Model(typ.Obj()).Collect().Values) > 0 { + if typ.Obj().Pkg().Path() == m.Imports.Self { + return fmt.Sprintf("%s%s.$zero", prefix, jsid(typ.Obj().Name())), true + } else { + return fmt.Sprintf("%s.%s.$zero", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true + } + } else { + return m.JSDefault(typ.Underlying(), quoted), true + } +} + +// renderStructDefault outputs the Javascript representation +// of the zero value for the given struct type. +func (m *module) renderStructDefault(typ *types.Struct) string { + if collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler { + return "null" + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return `""` + } + + info := m.collector.Struct(typ) + info.Collect() + + var builder strings.Builder + + builder.WriteRune('{') + for i, field := range info.Fields { + if field.Optional { + continue + } + + if i > 0 { + builder.WriteString(", ") + } + + builder.WriteRune('"') + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteRune('"') + + builder.WriteString(": ") + + builder.WriteString(m.JSDefault(field.Type, field.Quoted)) + } + builder.WriteRune('}') + + return builder.String() +} diff --git a/v3/internal/generator/render/doc.go b/v3/internal/generator/render/doc.go new file mode 100644 index 000000000..8ed46d9c5 --- /dev/null +++ b/v3/internal/generator/render/doc.go @@ -0,0 +1,136 @@ +package render + +import ( + "bufio" + "bytes" + "go/ast" + "strings" + "unicode" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// hasdoc checks whether the given comment group contains actual doc comments. +func hasdoc(group *ast.CommentGroup) bool { + if group == nil { + return false + } + + // TODO: this is horrible, make it more efficient? + return strings.ContainsFunc(group.Text(), func(r rune) bool { return !unicode.IsSpace(r) }) +} + +var commentTerminator = []byte("*/") + +// jsdoc splits the given comment into lines and rewrites it as follows: +// - first, line terminators are stripped; +// - then a line terminator, the indent string and ' * ' +// are prepended to each line; +// - occurrences of the comment terminator '*/' are replaced with '* /' +// to avoid accidentally terminating the surrounding comment. +// +// All lines thus modified are joined back together. +// +// The returned string can be inserted in a multiline JSDoc comment +// with the given indentation. +func jsdoc(comment string, indent string) string { + var builder strings.Builder + prefix := []byte(Newline + indent + " * ") + + scanner := bufio.NewScanner(bytes.NewReader([]byte(comment))) + for scanner.Scan() { + line := scanner.Bytes() + + // Prepend prefix. + builder.Write(prefix) + + // Escape comment terminators. + for t := bytes.Index(line, commentTerminator); t >= 0; t = bytes.Index(line, commentTerminator) { + builder.Write(line[:t+1]) + builder.WriteRune(' ') + line = line[t+1:] + } + + builder.Write(line) + } + + return builder.String() +} + +// jsdocline removes all newlines in the given comment +// and escapes comment terminators using the same strategy as jsdoc. +func jsdocline(comment string) string { + var builder strings.Builder + + scanner := bufio.NewScanner(bytes.NewReader([]byte(comment))) + for scanner.Scan() { + line := bytes.TrimSpace(scanner.Bytes()) + if len(line) == 0 { + // Skip empty lines. + continue + } + + // Prepend space to separate lines. + builder.WriteRune(' ') + + // Escape comment terminators. + for t := bytes.Index(line, commentTerminator); t >= 0; t = bytes.Index(line, commentTerminator) { + builder.Write(line[:t+1]) + builder.WriteRune(' ') + line = line[t+1:] + } + + builder.Write(line) + } + + // Return resulting string, but skip initial space. + return builder.String()[1:] +} + +// isjsdocid returns true if the given string is a valid ECMAScript identifier, +// excluding unicode escape sequences. This is the property name format supported by JSDoc. +func isjsdocid(name string) bool { + for i, r := range name { + if i == 0 && !id_start(r) && r != '$' && r != '_' { + return false + } else if i > 0 && !id_continue(r) && r != '$' { + return false + } + } + return true +} + +// isjsdocobj returns true if all field names in the given model +// are valid jsdoc property names. +func isjsdocobj(model *collect.ModelInfo) bool { + if len(model.Fields) == 0 { + return false + } + for _, decl := range model.Fields { + for _, field := range decl { + if !isjsdocid(field.JsonName) { + return false + } + } + } + return true +} + +// id_start returns true if the given rune is in the ID_Start category +// according to UAX#31 (https://unicode.org/reports/tr31/). +func id_start(r rune) bool { + return (unicode.IsLetter(r) || + unicode.Is(unicode.Nl, r) || + unicode.Is(unicode.Other_ID_Start, r)) && !unicode.Is(unicode.Pattern_Syntax, r) && !unicode.Is(unicode.Pattern_White_Space, r) +} + +// id_continue returns true if the given rune is in the ID_Continue category +// according to UAX#31 (https://unicode.org/reports/tr31/). +func id_continue(r rune) bool { + return (id_start(r) || + unicode.Is(unicode.Mn, r) || + unicode.Is(unicode.Mc, r) || + unicode.Is(unicode.Nd, r) || + unicode.Is(unicode.Pc, r) || + unicode.Is(unicode.Other_ID_Continue, r)) && !unicode.Is(unicode.Pattern_Syntax, r) && !unicode.Is(unicode.Pattern_White_Space, r) +} diff --git a/v3/internal/generator/render/functions.go b/v3/internal/generator/render/functions.go new file mode 100644 index 000000000..6123095e8 --- /dev/null +++ b/v3/internal/generator/render/functions.go @@ -0,0 +1,107 @@ +package render + +import ( + "fmt" + "go/types" + "math/big" + "strconv" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// tmplFunctions holds a map of utility functions +// that should be available in every template. +var tmplFunctions = template.FuncMap{ + "fixext": fixext, + "hasdoc": hasdoc, + "isjsdocid": isjsdocid, + "isjsdocobj": isjsdocobj, + "istpalias": istpalias, + "jsdoc": jsdoc, + "jsdocline": jsdocline, + "jsid": jsid, + "jsimport": jsimport, + "jsparam": jsparam, + "jsvalue": jsvalue, + "modelinfo": modelinfo, + "typeparam": typeparam, + "unalias": types.Unalias, +} + +// fixext replaces a *.ts extension with *.js in the given string. +// This is necessary to allow emitting javascript with the Typescript compiler. +func fixext(path string) string { + if strings.HasSuffix(path, ".ts") { + return path[:len(path)-3] + ".js" + } else { + return path + } +} + +// jsimport formats an external import name +// by joining the name with its occurrence index. +// Names are modified even when the index is 0 +// to avoid collisions with Go identifiers. +func jsimport(info collect.ImportInfo) string { + return fmt.Sprintf("%s$%d", info.Name, info.Index) +} + +// jsparam renders the JS name of a parameter. +// Blank parameters are replaced with a dollar sign followed by the given index. +// Non-blank parameters are escaped by [jsid]. +func jsparam(index int, param *collect.ParamInfo) string { + if param.Blank { + return "$" + strconv.Itoa(index) + } else { + return jsid(param.Name) + } +} + +// typeparam renders the TS name of a type parameter. +// Blank parameters are replaced with a double dollar sign +// followed by the given index. +// Non-blank parameters are escaped with jsid. +func typeparam(index int, param string) string { + if param == "" || param == "_" { + return "$$" + strconv.Itoa(index) + } else { + return jsid(param) + } +} + +// jsvalue renders a Go constant value to its Javascript representation. +func jsvalue(value any) string { + switch v := value.(type) { + case bool: + if v { + return "true" + } else { + return "false" + } + case string: + return fmt.Sprintf(`"%s"`, template.JSEscapeString(v)) + case int64: + return strconv.FormatInt(v, 10) + case *big.Int: + return v.String() + case *big.Float: + return v.Text('e', -1) + case *big.Rat: + return v.RatString() + } + + // Fall back to undefined. + return "(void(0))" +} + +// istpalias determines whether typ is an alias +// that when uninstantiated resolves to a typeparam. +func istpalias(typ types.Type) bool { + if alias, ok := typ.(*types.Alias); ok { + return collect.IsTypeParam(alias.Origin()) + } + + return false +} diff --git a/v3/internal/generator/render/identifier.go b/v3/internal/generator/render/identifier.go new file mode 100644 index 000000000..7a7b89f9d --- /dev/null +++ b/v3/internal/generator/render/identifier.go @@ -0,0 +1,92 @@ +package render + +import ( + "slices" +) + +// jsid escapes identifiers that match JS/TS reserved words +// by prepending a dollar sign. +func jsid(ident string) string { + if _, reserved := slices.BinarySearch(protectedWords, ident); reserved { + return "$" + ident + } + return ident +} + +func init() { + // Ensure reserved words are sorted in ascending lexicographical order. + slices.Sort(protectedWords) +} + +// protectedWords is a list of JS + TS words that are either reserved +// or have special meaning. Keep in ascending lexicographical order +// for best startup performance. +var protectedWords = []string{ + "JSON", + "Object", + "any", + "arguments", + "as", + "async", + "await", + "boolean", + "break", + "case", + "catch", + "class", + "const", + "constructor", + "continue", + "debugger", + "declare", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "from", + "function", + "get", + "if", + "implements", + "import", + "in", + "instanceof", + "interface", + "let", + "module", + "namespace", + "new", + "null", + "number", + "of", + "package", + "private", + "protected", + "public", + "require", + "return", + "set", + "static", + "string", + "super", + "switch", + "symbol", + "this", + "throw", + "true", + "try", + "type", + "typeof", + "undefined", + "var", + "void", + "while", + "with", + "yield", +} diff --git a/v3/internal/generator/render/info.go b/v3/internal/generator/render/info.go new file mode 100644 index 000000000..3947f117a --- /dev/null +++ b/v3/internal/generator/render/info.go @@ -0,0 +1,77 @@ +package render + +import ( + "regexp" + "strings" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// modelInfo gathers useful information about a model. +type modelInfo struct { + HasValues bool + IsEnum bool + + IsAlias bool + IsClassAlias bool + IsTypeAlias bool + + IsClassOrInterface bool + IsInterface bool + IsClass bool + + Template struct { + Params string + ParamList string + CreateList string + } +} + +// createParamRegex must match type parameter creation strings as generated by [modelinfo]. +var createParamRegex = regexp.MustCompile(`\$\$createParam[^\s,)]*`) + +// modelinfo gathers and returns useful information about the given model. +func modelinfo(model *collect.ModelInfo, useInterfaces bool) (info modelInfo) { + info.HasValues = len(model.Values) > 0 + info.IsEnum = info.HasValues && !model.Alias + + info.IsAlias = !info.IsEnum && model.Type != nil + info.IsClassAlias = info.IsAlias && model.Predicates.IsClass && !useInterfaces + info.IsTypeAlias = info.IsAlias && !info.IsClassAlias + + info.IsClassOrInterface = !info.IsEnum && !info.IsAlias + info.IsInterface = info.IsClassOrInterface && (model.Alias || useInterfaces) + info.IsClass = info.IsClassOrInterface && !info.IsInterface + + if len(model.TypeParams) > 0 { + var params, paramList, createList strings.Builder + + paramList.WriteRune('<') + createList.WriteRune('(') + + for i, param := range model.TypeParams { + param = typeparam(i, param) + + if i > 0 { + params.WriteRune(',') + paramList.WriteString(", ") + createList.WriteString(", ") + } + + params.WriteString(param) + paramList.WriteString(param) + + createList.WriteString("$$createParam") + createList.WriteString(param) + } + + paramList.WriteRune('>') + createList.WriteRune(')') + + info.Template.Params = params.String() + info.Template.ParamList = paramList.String() + info.Template.CreateList = createList.String() + } + + return +} diff --git a/v3/internal/generator/render/module.go b/v3/internal/generator/render/module.go new file mode 100644 index 000000000..3611c939a --- /dev/null +++ b/v3/internal/generator/render/module.go @@ -0,0 +1,26 @@ +package render + +import ( + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" + "golang.org/x/tools/go/types/typeutil" +) + +// module gathers data that is used when rendering a single JS/TS module. +type module struct { + *Renderer + *flags.GenerateBindingsOptions + + Imports *collect.ImportMap + + postponedCreates typeutil.Map +} + +// Runtime returns the import path for the Wails JS runtime module. +func (m *module) Runtime() string { + if m.UseBundledRuntime { + return "/wails/runtime.js" + } else { + return "@wailsio/runtime" + } +} diff --git a/v3/internal/generator/render/renderer.go b/v3/internal/generator/render/renderer.go new file mode 100644 index 000000000..5ddee85c2 --- /dev/null +++ b/v3/internal/generator/render/renderer.go @@ -0,0 +1,141 @@ +package render + +import ( + "go/types" + "io" + "slices" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// Renderer holds the template set for a given configuration. +// It provides methods for rendering various output modules. +type Renderer struct { + options *flags.GenerateBindingsOptions + collector *collect.Collector + + ext string + + service *template.Template + typedefs *template.Template +} + +// NewRenderer initialises a code renderer +// for the given configuration and data collector. +func NewRenderer(options *flags.GenerateBindingsOptions, collector *collect.Collector) *Renderer { + ext := ".js" + if options.TS { + ext = ".ts" + } + + return &Renderer{ + options: options, + collector: collector, + + ext: ext, + + service: tmplService[tmplLanguage(options.TS)], + typedefs: tmplModels[tmplLanguage(options.TS)], + } +} + +// ServiceFile returns the standard name of a service file +// for the given struct name, with the appropriate extension. +func (renderer *Renderer) ServiceFile(name string) string { + return strings.ToLower(name) + renderer.ext +} + +// ModelsFile returns the standard name of a models file +// with the appropriate extension. +func (renderer *Renderer) ModelsFile() string { + return renderer.options.ModelsFilename + renderer.ext +} + +// IndexFile returns the standard name of a package index file +// with the appropriate extension. +func (renderer *Renderer) IndexFile() string { + return renderer.options.IndexFilename + renderer.ext +} + +// Service renders binding code for the given service type to w. +func (renderer *Renderer) Service(w io.Writer, info *collect.ServiceInfo) error { + return renderer.service.Execute(w, &struct { + module + Service *collect.ServiceInfo + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: info.Imports, + }, + info, + }) +} + +// Typedefs renders type definitions for the given list of models. +func (renderer *Renderer) Models(w io.Writer, imports *collect.ImportMap, models []*collect.ModelInfo) error { + if !renderer.options.UseInterfaces { + // Sort class aliases after the class they alias. + // Works in amortized linear time thanks to an auxiliary map. + + // Track postponed class aliases and their dependencies. + aliases := make(map[types.Object][]*collect.ModelInfo, len(models)) + + models = slices.Clone(models) + for i, j := 0, 0; i < len(models); i++ { + if models[i].Type != nil && models[i].Predicates.IsClass { + // models[i] is a class alias: + // models[i].Type is guaranteed to be + // either an alias or a named type + obj := models[i].Type.(interface{ Obj() *types.TypeName }).Obj() + if obj.Pkg().Path() == imports.Self { + // models[i] aliases a type from the current module. + if a, ok := aliases[obj]; !ok || len(a) > 0 { + // The aliased type has not been visited already, postpone. + aliases[obj] = append(a, models[i]) + continue + } + } + } + + // Append models[i]. + models[j] = models[i] + j++ + + // Keep appending aliases whose aliased type has been just appended. + for k := j - 1; k < j; k++ { + a := aliases[models[k].Object()] + aliases[models[k].Object()] = nil // Mark aliased model as visited + j += copy(models[j:], a) + } + } + } + + return renderer.typedefs.Execute(w, &struct { + module + Models []*collect.ModelInfo + }{ + module{ + Renderer: renderer, + GenerateBindingsOptions: renderer.options, + Imports: imports, + }, + models, + }) +} + +// Index renders the given package index to w. +func (renderer *Renderer) Index(w io.Writer, index *collect.PackageIndex) error { + return tmplIndex.Execute(w, &struct { + *collect.PackageIndex + *Renderer + *flags.GenerateBindingsOptions + }{ + index, + renderer, + renderer.options, + }) +} diff --git a/v3/internal/generator/render/templates.go b/v3/internal/generator/render/templates.go new file mode 100644 index 000000000..5aa7398f5 --- /dev/null +++ b/v3/internal/generator/render/templates.go @@ -0,0 +1,39 @@ +package render + +import ( + "embed" + "strings" + "text/template" +) + +//go:embed templates/*.tmpl +var templates embed.FS + +type tmplLanguage bool + +const tmplJS, tmplTS tmplLanguage = false, true + +var tmplService = map[tmplLanguage]*template.Template{ + tmplJS: template.Must(template.New("service.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/service.js.tmpl")), + tmplTS: template.Must(template.New("service.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/service.ts.tmpl")), +} + +var tmplModels = map[tmplLanguage]*template.Template{ + tmplJS: template.Must(template.New("models.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/models.js.tmpl")), + tmplTS: template.Must(template.New("models.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/models.ts.tmpl")), +} + +var tmplIndex = template.Must(template.New("index.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/index.tmpl")) + +var Newline string + +func init() { + var builder strings.Builder + + err := template.Must(template.New("newline.tmpl").ParseFS(templates, "templates/newline.tmpl")).Execute(&builder, nil) + if err != nil { + panic(err) + } + + Newline = builder.String() +} diff --git a/v3/internal/generator/render/templates/index.tmpl b/v3/internal/generator/render/templates/index.tmpl new file mode 100644 index 000000000..fd067bf50 --- /dev/null +++ b/v3/internal/generator/render/templates/index.tmpl @@ -0,0 +1,102 @@ +{{$renderer := .}} +{{- $useInterfaces := .UseInterfaces}} +{{- $models := (fixext .ModelsFile)}} +{{- if not .TS -}} +// @ts-check +{{end -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT +{{$hasDocs := false}} +{{- range .Package.Docs}} +{{- if hasdoc .}}{{$hasDocs := true}}{{break}}{{end}} +{{- end}} +{{- if $hasDocs}} +/** +{{- range .Package.Docs}} +{{- jsdoc .Text ""}} +{{- end}} + * @module + */ +{{end}} +{{- if .Services}} +{{- range .Services}}{{if .Internal}}{{break}}{{end}} +import * as {{jsid .Name}} from "./{{js (fixext ($renderer.ServiceFile .Name))}}"; +{{- end}} +export { +{{- range $i, $service := .Services}} + {{- if .Internal}}{{break}}{{end}} + {{- if gt $i 0}},{{end}} + {{jsid .Name}} +{{- end}} +}; +{{end}} + +{{- $hasObjects := false}} +{{- $hasTypes := false}} + +{{- range $model := .Models}} +{{- if $model.Internal}}{{break}}{{end}} + +{{- $info := modelinfo $model $useInterfaces }} + +{{- if or $info.HasValues $info.IsClassAlias $info.IsClass}} +{{- if not $hasObjects}} + {{- $hasObjects = true}} +export { +{{- else}},{{end}} + {{jsid $model.Name}} +{{- else}} + {{- $hasTypes = true}} +{{- end}} +{{- end}} +{{- if $hasObjects}} +} from "./{{js $models}}"; +{{end}} + +{{- if $hasTypes}} +{{- $hasTypes = false}} + +{{- if .TS}} +export type { +{{- else}} +import * as $models from "./{{js $models}}"; +{{end}} +{{- range $model := .Models}} +{{- if $model.Internal}}{{break}}{{end}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or $info.HasValues $info.IsClassAlias $info.IsClass}}{{continue}}{{end}} + +{{- if $renderer.TS}} + {{- if $hasTypes}},{{end}} + {{jsid $model.Name}} +{{- else}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if $template.ParamList}} + * @template {{$template.Params}} +{{- end}} + * @typedef {$models.{{jsid $model.Name}}{{$template.ParamList -}} } {{jsid $model.Name}} + */ +{{end}} + +{{- $hasTypes = true}} +{{- end}} + +{{- if .TS}} +} from "./{{js $models}}"; +{{end}} + +{{- end}} +{{- range .Package.Injections}} +{{.}} +{{- end}}{{if .Package.Injections}} +{{end -}} diff --git a/v3/internal/generator/render/templates/models.js.tmpl b/v3/internal/generator/render/templates/models.js.tmpl new file mode 100644 index 000000000..6c0e98b8b --- /dev/null +++ b/v3/internal/generator/render/templates/models.js.tmpl @@ -0,0 +1,207 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useInterfaces := .UseInterfaces}} +{{- $imports := $module.Imports -}} +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT +{{if not $useInterfaces}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "{{js $runtime}}"; +{{end -}} +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- range $model := .Models}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or $template.ParamList (hasdoc $model.Decl.Doc) (hasdoc $model.Doc) $info.IsEnum $info.IsTypeAlias $info.IsInterface}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if and $template.ParamList (not $info.IsClassAlias)}} + * @template {{$template.Params}} +{{- end}} +{{- if $info.IsEnum}} + * @readonly + * @enum { {{- $module.JSType $model.Type -}} } +{{- else if $info.IsTypeAlias}} + * @typedef { {{- $module.JSType $model.Type -}} } {{jsid $model.Name}} +{{- else if $info.IsInterface}} +{{- if isjsdocobj $model}} + * @typedef {Object} {{jsid $model.Name}} +{{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + * @property { {{- $module.JSFieldType $field.StructField -}} } + {{- if $field.Optional}} [{{else}} {{end}}{{$field.JsonName}}{{if $field.Optional}}]{{end}} + {{- if hasdoc $field.Decl.Doc}} - {{jsdocline $field.Decl.Doc.Text}}{{end}} +{{- end}}{{end}} +{{- else}} + * @typedef { { +{{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + * "{{js $field.JsonName}}"{{if $field.Optional}}?{{end}}: {{$module.JSFieldType $field.StructField}}, +{{- end}}{{end}} + * } } {{jsid $model.Name}} +{{- end}} +{{- end}} + */ +{{- end}} +{{- if $info.HasValues}} +{{- if not $info.IsEnum}} + +/** + * Predefined constants for type {{jsid $model.Name}}. + * @namespace + */ +{{- end}} +export const {{jsid $model.Name}} = { +{{- if $info.IsEnum}} + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: {{$module.JSDefault $model.Type false}}, +{{end}} +{{- range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}}: {{jsvalue $value.Value}}, +{{- end}}{{end}}{{end}} +}; +{{else if $info.IsClassAlias}} +export const {{jsid $model.Name}} = {{if istpalias $model.Type -}} + {{$module.JSType (unalias $model.Type).Origin}}; +{{- else -}} + {{$module.JSType $model.Type.Origin}}; +{{- end}} + +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} +{{- if $template.ParamList}} + * @template {{$template.Params}} +{{- end}} + * @typedef { {{- $module.JSType $model.Type -}} } {{jsid $model.Name}} + */ +{{else if and $info.IsClass}} +export class {{jsid $model.Name}} { + /** + * Creates a new {{jsid $model.Name}} instance. + * @param {Partial<{{jsid $model.Name}}{{$template.ParamList}}>} [$$source = {}] - The source object to create the {{jsid $model.Name}}. + */ + constructor($$source = {}) { + {{- range $decl := $model.Fields}}{{range $j, $field := $decl}} + {{- /* + In JS we need to set all properties explicitly + because JSDoc has no support for arbitrary property names yet. + See https://github.com/jsdoc/jsdoc/issues/1468 + + For optional fields we make the initialization code unreachable + and cast the false condition to any to prevent any complaint from Typescript. + */}} + if ({{if $field.Optional}}/** @type {any} */(false){{else}}!("{{js $field.JsonName}}" in $$source){{end}}) { + /** + {{- if and (eq $j 0) (hasdoc $field.Decl.Doc)}} + {{- jsdoc $field.Decl.Doc.Text " "}} + {{- end}} + * @member + * @type { {{- $module.JSFieldType $field.StructField}}{{if $field.Optional}} | undefined{{end -}} } + */ + this["{{js $field.JsonName}}"] = {{if $field.Optional}}undefined{{else}}{{$module.JSDefault $field.Type $field.Quoted}}{{end}}; + } + {{- end}}{{end}} + + Object.assign(this, $$source); + } + + /** + {{- if $template.ParamList}} + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class {{jsid $model.Name}}. + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + * @template [{{$param}}=any] + {{- end}} + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + * @param {(source: any) => {{$param -}} } $$createParam{{$param}} + {{- end}} + * @returns {($$source?: any) => {{jsid $model.Name}}{{$template.ParamList -}} } + {{- else}} + * Creates a new {{jsid $model.Name}} instance from a string or object. + * @param {any} [$$source = {}] + * @returns { {{- jsid $model.Name -}} } + {{- end}} + */ + static createFrom{{if $template.ParamList}}{{$template.CreateList}}{{else}}($$source = {}){{end}} { + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- $create := ($module.JSCreateWithParams $field.Type $template.CreateList)}} + {{- if ne $create "$Create.Any"}} + const $$createField{{$i}}_{{$j}} = {{$create}}; + {{- end}} + {{- end}}{{end}} + {{- $indent := ""}} + {{- if $template.ParamList}} + {{- $indent = " "}} + return ($$source = {}) => { + {{- end}} + {{$indent}}let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- if $module.NeedsCreate $field.Type}} + {{$indent}}if ("{{js $field.JsonName}}" in $$parsedSource) { + {{$indent}} $$parsedSource["{{js $field.JsonName}}"] = $$createField{{$i}}_{{$j}}($$parsedSource["{{js $field.JsonName}}"]); + {{$indent -}} } + {{- end}} + {{- end}}{{end}} + {{$indent}}return new {{jsid $model.Name}}(/** @type {Partial<{{jsid $model.Name}}{{$template.ParamList}}>} */($$parsedSource)); + {{- if $template.ParamList}} + }; + {{- end}} + } +} +{{else}} +{{- /* Rendered as a @typedef */}} +{{end}} +{{- end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- if $useInterfaces}} +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; +{{end -}} diff --git a/v3/internal/generator/render/templates/models.ts.tmpl b/v3/internal/generator/render/templates/models.ts.tmpl new file mode 100644 index 000000000..a6073af08 --- /dev/null +++ b/v3/internal/generator/render/templates/models.ts.tmpl @@ -0,0 +1,189 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useInterfaces := .UseInterfaces}} +{{- $imports := $module.Imports -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT +{{if not $useInterfaces}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "{{js $runtime}}"; +{{end -}} +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- range $model := .Models}} + +{{- $info := modelinfo $model $useInterfaces }} +{{- $template := $info.Template }} + +{{- if or (hasdoc $model.Decl.Doc) (hasdoc $model.Doc)}} +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} + */ +{{- end}} +{{- if $info.IsEnum}} +export enum {{jsid $model.Name}} { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = {{$module.JSDefault $model.Type false}}, +{{range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}} = {{jsvalue $value.Value}}, + {{- end}}{{end}}{{end}} +}; +{{else if $info.IsClassAlias}} +export const {{jsid $model.Name}} = {{if istpalias $model.Type -}} + {{$module.JSType (unalias $model.Type).Origin}}; +{{- else -}} + {{$module.JSType $model.Type.Origin}}; +{{- end}} +{{- if or (hasdoc $model.Decl.Doc) (hasdoc $model.Doc)}} + +/** +{{- if hasdoc $model.Decl.Doc}} +{{- jsdoc $model.Decl.Doc.Text ""}}{{if hasdoc $model.Doc}} + *{{end}} +{{- end}} +{{- if hasdoc $model.Doc}} +{{- jsdoc $model.Doc.Text ""}} +{{- end}} + */ +{{- end}} +export type {{jsid $model.Name}}{{$template.ParamList}} = {{$module.JSType $model.Type}}; +{{else if $info.IsTypeAlias}} +export type {{jsid $model.Name}}{{$template.ParamList}} = {{$module.JSType $model.Type}}; +{{- if $info.HasValues}} + +/** + * Predefined constants for type {{jsid $model.Name}}. + * @namespace + */ +export const {{jsid $model.Name}} = { +{{- range $i, $decl := $model.Values}}{{range $j, $spec := $decl}}{{range $k, $value := $spec}} + {{- if and (ne $i 0) (eq $j 0) (eq $k 0)}} +{{end}} + {{- if or (and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)) (and (eq $k 0) (hasdoc $value.Spec.Doc))}} + {{- if gt $j 0}} +{{end}} + /** + {{- if and (eq $j 0) (eq $k 0) (hasdoc $value.Decl.Doc)}} + {{- jsdoc $value.Decl.Doc.Text " "}}{{if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + *{{end}} + {{- end}} + {{- if and (eq $k 0) (hasdoc $value.Spec.Doc)}} + {{- jsdoc $value.Spec.Doc.Text " "}} + {{- end}} + */ + {{- end}} + {{jsid $value.Name}}: {{jsvalue $value.Value}}, +{{- end}}{{end}}{{end}} +}; +{{- end}} +{{else if $info.IsClassOrInterface}} +export {{if $info.IsInterface}}interface{{else}}class{{end}} {{jsid $model.Name}}{{$template.ParamList}} { + {{- range $i, $decl := $model.Fields}}{{range $j, $field := $decl}} + {{- if and (eq $j 0) (hasdoc $field.Decl.Doc)}} + {{- if gt $i 0}} +{{end}} + /** + {{- jsdoc $field.Decl.Doc.Text " "}} + */ + {{- end}} + "{{js $field.JsonName}}"{{if $field.Optional}}?{{end}}: {{$module.JSFieldType $field.StructField}}; + {{- end}}{{end}} +{{- if $info.IsClass}} + + /** Creates a new {{jsid $model.Name}} instance. */ + constructor($$source: Partial<{{jsid $model.Name}}{{$template.ParamList}}> = {}) { + {{- range $spec := $model.Fields}}{{range $i, $field := $spec}}{{if not $field.Optional}} + if (!("{{js $field.JsonName}}" in $$source)) { + this["{{js $field.JsonName}}"] = {{$module.JSDefault $field.Type $field.Quoted}}; + } + {{- end}}{{end}}{{end}} + + Object.assign(this, $$source); + } + + /** + {{- if $template.ParamList}} + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class {{jsid $model.Name}}. + {{- else}} + * Creates a new {{jsid $model.Name}} instance from a string or object. + {{- end}} + */ + static createFrom{{if $template.ParamList}}< + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + {{- if gt $i 0}}, {{end -}} + {{$param}} = any + {{- end}}>{{end}}({{if $template.ParamList}} + {{- range $i, $param := $model.TypeParams}} + {{- $param = (typeparam $i $param)}} + {{- if gt $i 0}}, {{end -}} + $$createParam{{$param}}: (source: any) => {{$param}}{{end -}} + {{else}}$$source: any = {}{{end}}): + {{- if $template.ParamList}} ($$source?: any) =>{{end}} {{jsid $model.Name}}{{$template.ParamList}} { + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- $create := ($module.JSCreateWithParams $field.Type $template.CreateList)}} + {{- if ne $create "$Create.Any"}} + const $$createField{{$i}}_{{$j}} = {{$create}}; + {{- end}} + {{- end}}{{end}} + {{- $indent := ""}} + {{- if $template.ParamList}} + {{- $indent = " "}} + return ($$source: any = {}) => { + {{- end}} + {{$indent}}let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + {{- range $i, $spec := $model.Fields}}{{range $j, $field := $spec}} + {{- if $module.NeedsCreate $field.Type}} + {{$indent}}if ("{{js $field.JsonName}}" in $$parsedSource) { + {{$indent}} $$parsedSource["{{js $field.JsonName}}"] = $$createField{{$i}}_{{$j}}($$parsedSource["{{js $field.JsonName}}"]); + {{$indent -}} } + {{- end}} + {{- end}}{{end}} + {{$indent}}return new {{jsid $model.Name}}{{$template.ParamList}}($$parsedSource as Partial<{{jsid $model.Name}}{{$template.ParamList}}>); + {{- if $template.ParamList}} + }; + {{- end}} + } +{{- end}} +} +{{end}} +{{- end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 16) (eq (slice $create 1 16) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end -}} diff --git a/v3/internal/generator/render/templates/newline.tmpl b/v3/internal/generator/render/templates/newline.tmpl new file mode 100644 index 000000000..d28790668 --- /dev/null +++ b/v3/internal/generator/render/templates/newline.tmpl @@ -0,0 +1,6 @@ +{{/* This template should render to a single newline (either LF or CRLF). */}} +{{/* + Git might be configured to rewrite LF newlines to CRLF + in templates, test cases and data, especially on Windows. + Having a newline template enables detection of the current newline mode. +*/ -}} diff --git a/v3/internal/generator/render/templates/service.js.tmpl b/v3/internal/generator/render/templates/service.js.tmpl new file mode 100644 index 000000000..f71e055bf --- /dev/null +++ b/v3/internal/generator/render/templates/service.js.tmpl @@ -0,0 +1,100 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useNames := $module.UseNames}} +{{- $useInterfaces := $module.UseInterfaces}} +{{- $imports := $module.Imports}} +{{- with .Service -}} +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT +{{if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + * @module + */ +{{end}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise{{if not $useInterfaces}}, Create as $Create{{end}} } from "{{js $runtime}}"; +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./{{js $models}}"; +{{end}} +{{- range .Methods}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} +{{- range $i, $param := .Params}} + * @param { {{- $module.JSType .Type}}{{if .Variadic}}[]{{end -}} } {{jsparam $i .}} +{{- end}} + * @returns {$CancellablePromise< + {{- if eq 0 (len .Results) -}} + void + {{- else if eq 1 (len .Results)}} + {{- $module.JSType (index .Results 0)}} + {{- else -}} + [{{range $i, $result := .Results}} + {{- if gt $i 0}}, {{end}} + {{- $module.JSType $result}} + {{- end}}] + {{- end}}>} + */ +{{if not .Internal}}export {{end}}function {{.Name}}({{range $i, $param := .Params -}} + {{- if gt $i 0}}, {{end}} + {{- if .Variadic}}...{{end}} + {{- jsparam $i .}} +{{- end}}) { + {{- if $useNames}} + return $Call.ByName("{{js .FQN}}" + {{- else}} + return $Call.ByID({{.ID}} + {{- end}}{{range $i, $param := .Params}}, {{jsparam $i .}}{{end}}) + {{- if or $useInterfaces (not .Results) ($module.SkipCreate .Results) -}} + ; + {{- else -}} + .then(/** @type {($result: any) => any} */(($result) => { + {{- if eq 1 (len .Results)}} + return {{$module.JSCreate (index .Results 0)}}($result); + {{- else}} + {{- range $i, $type := .Results}} + {{- $create := ($module.JSCreate $type)}} + {{- if ne $create "$Create.Any"}} + $result[{{$i}}] = {{$create}}($result[{{$i}}]); + {{- end}}{{end}} + return $result; + {{- end}} + })); + {{- end}} +} +{{end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- range .Injections}} +{{.}} +{{- end}}{{if .Injections}} +{{end}}{{end -}} diff --git a/v3/internal/generator/render/templates/service.ts.tmpl b/v3/internal/generator/render/templates/service.ts.tmpl new file mode 100644 index 000000000..4db90fe60 --- /dev/null +++ b/v3/internal/generator/render/templates/service.ts.tmpl @@ -0,0 +1,97 @@ +{{$module := .}} +{{- $runtime := $module.Runtime}} +{{- $models := (fixext $module.ModelsFile)}} +{{- $useNames := $module.UseNames}} +{{- $useInterfaces := $module.UseInterfaces}} +{{- $imports := $module.Imports}} +{{- with .Service -}} +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT +{{if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if hasdoc .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + * @module + */ +{{end}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise{{if not $useInterfaces}}, Create as $Create{{end}} } from "{{js $runtime}}"; +{{range $imports.External}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}"; +{{- end}}{{if $imports.External}} +{{end}} +{{- if $imports.ImportModels}} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./{{js $models}}"; +{{end}} +{{- range .Methods}} +{{- if or (hasdoc .Decl.Doc) (hasdoc .Doc)}} +/** +{{- if hasdoc .Decl.Doc}} +{{- jsdoc .Decl.Doc.Text ""}}{{if hasdoc .Doc}} + *{{end}} +{{- end}} +{{- if .Doc}} +{{- jsdoc .Doc.Text ""}} +{{- end}} + */ +{{- end}} +{{if not .Internal}}export {{end}}function {{.Name}}({{range $i, $param := .Params -}} + {{- if gt $i 0}}, {{end}} + {{- if .Variadic}}...{{end}} + {{- jsparam $i .}}: {{$module.JSType .Type}}{{if .Variadic}}[]{{end}} +{{- end}}): $CancellablePromise< + {{- if eq 0 (len .Results) -}} + void + {{- else if eq 1 (len .Results)}} + {{- $module.JSType (index .Results 0)}} + {{- else -}} + [{{range $i, $result := .Results}} + {{- if gt $i 0}}, {{end}} + {{- $module.JSType $result}} + {{- end}}] + {{- end}}> { + {{- if $useNames}} + return $Call.ByName("{{js .FQN}}" + {{- else}} + return $Call.ByID({{.ID}} + {{- end}}{{range $i, $param := .Params}}, {{jsparam $i .}}{{end}}) + {{- if or $useInterfaces (not .Results) ($module.SkipCreate .Results) -}} + ; + {{- else -}} + .then(($result: any) => { + {{- if eq 1 (len .Results)}} + return {{$module.JSCreate (index .Results 0)}}($result); + {{- else}} + {{- range $i, $type := .Results}} + {{- $create := ($module.JSCreate $type)}} + {{- if ne $create "$Create.Any"}} + $result[{{$i}}] = {{$create}}($result[{{$i}}]); + {{- end}}{{end}} + return $result; + {{- end}} + }); + {{- end}} +} +{{end}} +{{- $postponed := $module.PostponedCreates}} +{{- if $postponed}} +// Private type creation functions +{{- range $i, $create := $postponed}} +{{if and (ge (len $create) 16) (eq (slice $create 1 16) "function $$init")}}var {{else}}const {{end -}} +$$createType{{$i}} = {{$create}}; +{{- end}} +{{end}} +{{- range .Injections}} +{{.}} +{{- end}}{{if .Injections}} +{{end}}{{end -}} diff --git a/v3/internal/generator/render/type.go b/v3/internal/generator/render/type.go new file mode 100644 index 000000000..2c880154a --- /dev/null +++ b/v3/internal/generator/render/type.go @@ -0,0 +1,265 @@ +package render + +import ( + "fmt" + "go/types" + "strings" + "text/template" + + "github.com/wailsapp/wails/v3/internal/generator/collect" +) + +// aliasOrNamed is a common interface for *types.Alias and *types.Named. +type aliasOrNamed interface { + types.Type + Obj() *types.TypeName +} + +// typeByteSlice caches the type-checker type for a slice of bytes. +var typeByteSlice = types.NewSlice(types.Universe.Lookup("byte").Type()) + +// JSType renders a Go type to its TypeScript representation, +// using the receiver's import map to resolve dependencies. +// +// JSType's output may be incorrect if m.Imports.AddType +// has not been called for the given type. +func (m *module) JSType(typ types.Type) string { + result, _ := m.renderType(typ, false) + return result +} + +// JSFieldType renders a struct field type to its TypeScript representation, +// using the receiver's import map to resolve dependencies. +// +// JSFieldType's output may be incorrect if m.Imports.AddType +// has not been called for the given type. +func (m *module) JSFieldType(field *collect.StructField) string { + result, _ := m.renderType(field.Type, field.Quoted) + return result +} + +// renderType provides the actual implementation of [module.Type]. +// It returns the rendered type and a boolean indicating whether +// the resulting expression describes a nullable type. +func (m *module) renderType(typ types.Type, quoted bool) (result string, nullable bool) { + switch t := typ.(type) { + case *types.Alias, *types.Named: + return m.renderNamedType(typ.(aliasOrNamed), quoted) + + case *types.Array, *types.Slice: + null := "" + if _, isSlice := typ.(*types.Slice); isSlice && m.UseInterfaces { + // In interface mode, record the fact that encoding/json marshals nil slices as null. + null = " | null" + } + + if types.Identical(typ, typeByteSlice) { + // encoding/json marshals byte slices as base64 strings + return "string" + null, null != "" + } + + elem, ptr := m.renderType(typ.(interface{ Elem() types.Type }).Elem(), false) + if ptr { + return fmt.Sprintf("(%s)[]%s", elem, null), null != "" + } else { + return fmt.Sprintf("%s[]%s", elem, null), null != "" + } + + case *types.Basic: + return m.renderBasicType(t, quoted), false + + case *types.Map: + return m.renderMapType(t) + + case *types.Pointer: + elem, nullable := m.renderType(t.Elem(), false) + if nullable { + return elem, nullable + } else { + return fmt.Sprintf("%s | null", elem), true + } + + case *types.Struct: + return m.renderStructType(t), false + + case *types.TypeParam: + pre, post := "", "" + if quoted { + pre, post = "(", " | string)" + } + return fmt.Sprintf("%s%s%s", pre, typeparam(t.Index(), t.Obj().Name()), post), false + } + + // Fall back to untyped mode. + return "any", false +} + +// renderBasicType outputs the TypeScript representation +// of the given basic type. +func (*module) renderBasicType(typ *types.Basic, quoted bool) string { + switch { + case typ.Info()&types.IsBoolean != 0: + if quoted { + return "`${boolean}`" + } else { + return "boolean" + } + + case typ.Info()&types.IsNumeric != 0 && typ.Info()&types.IsComplex == 0: + if quoted { + return "`${number}`" + } else { + return "number" + } + + case typ.Info()&types.IsString != 0: + if quoted { + return "`\"${string}\"`" + } else { + return "string" + } + } + + // Fall back to untyped mode. + if quoted { + return "string" + } else { + return "any" + } +} + +// renderMapType outputs the TypeScript representation of the given map type. +func (m *module) renderMapType(typ *types.Map) (result string, nullable bool) { + null := "" + if m.UseInterfaces { + // In interface mode, record the fact that encoding/json marshals nil slices as null. + null = " | null" + } + + key := "string" + elem, _ := m.renderType(typ.Elem(), false) + + // Test whether we can upgrade key rendering. + switch k := typ.Key().(type) { + case *types.Basic: + if k.Info()&types.IsString == 0 && collect.IsMapKey(k) { + // Render non-string basic type in quoted mode. + key = m.renderBasicType(k, true) + } + + case *types.Alias, *types.Named, *types.Pointer: + if collect.IsMapKey(k) { + if collect.IsStringAlias(k) { + // Alias or named type is a string and therefore + // safe to use as a JS object key. + if ptr, ok := k.(*types.Pointer); ok { + // Unwrap pointers to string aliases. + key, _ = m.renderType(ptr.Elem(), false) + } else { + key, _ = m.renderType(k, false) + } + } else if basic, ok := k.Underlying().(*types.Basic); ok && basic.Info()&types.IsString == 0 { + // Render non-string basic type in quoted mode. + key = m.renderBasicType(basic, true) + } + } + } + + return fmt.Sprintf("{ [_: %s]: %s }%s", key, elem, null), m.UseInterfaces +} + +// renderNamedType outputs the TS representation +// of the given named or alias type. +func (m *module) renderNamedType(typ aliasOrNamed, quoted bool) (result string, nullable bool) { + if typ.Obj().Pkg() == nil { + // Builtin alias or named type: render underlying type. + return m.renderType(typ.Underlying(), quoted) + } + + if quoted { + switch a := types.Unalias(typ).(type) { + case *types.Basic: + // Quoted mode for (alias of?) basic type: delegate. + return m.renderBasicType(a, quoted), false + case *types.TypeParam: + // Quoted mode for (alias of?) typeparam: delegate. + return m.renderType(a, quoted) + case *types.Named: + // Quoted mode for (alias of?) named type. + // WARN: Do not test with IsAny/IsStringAlias here!! We only want to catch marshalers. + if collect.MaybeJSONMarshaler(typ) == collect.NonMarshaler && collect.MaybeTextMarshaler(typ) == collect.NonMarshaler { + // No custom marshaling for this type. + if u, ok := a.Underlying().(*types.Basic); ok { + // Quoted mode for basic named type that is not a marshaler: delegate. + return m.renderBasicType(u, quoted), false + } + } + } + } + + var builder strings.Builder + + if typ.Obj().Pkg().Path() == m.Imports.Self { + if m.Imports.ImportModels { + builder.WriteString("$models.") + } + } else { + builder.WriteString(jsimport(m.Imports.External[typ.Obj().Pkg().Path()])) + builder.WriteRune('.') + } + builder.WriteString(jsid(typ.Obj().Name())) + + instance, _ := typ.(interface{ TypeArgs() *types.TypeList }) + if instance != nil { + // Render type arguments. + if targs := instance.TypeArgs(); targs != nil && targs.Len() > 0 { + builder.WriteRune('<') + for i := range targs.Len() { + if i > 0 { + builder.WriteString(", ") + } + arg, _ := m.renderType(targs.At(i), false) + builder.WriteString(arg) + } + builder.WriteRune('>') + } + } + + return builder.String(), false +} + +// renderStructType outputs the TS representation +// of the given anonymous struct type. +func (m *module) renderStructType(typ *types.Struct) string { + if collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler { + return "any" + } else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler { + return "string" + } + + info := m.collector.Struct(typ) + info.Collect() + + var builder strings.Builder + + builder.WriteRune('{') + for i, field := range info.Fields { + if i > 0 { + builder.WriteString(", ") + } + + builder.WriteRune('"') + template.JSEscape(&builder, []byte(field.JsonName)) + builder.WriteRune('"') + + if field.Optional { + builder.WriteRune('?') + } + + builder.WriteString(": ") + builder.WriteString(m.JSFieldType(field)) + } + builder.WriteRune('}') + + return builder.String() +} diff --git a/v3/internal/generator/service.go b/v3/internal/generator/service.go new file mode 100644 index 000000000..fcbfa9447 --- /dev/null +++ b/v3/internal/generator/service.go @@ -0,0 +1,102 @@ +package generator + +import ( + "go/types" + "path/filepath" +) + +// generateService collects information +// and generates JS/TS binding code +// for the given service type object. +func (generator *Generator) generateService(obj *types.TypeName) { + generator.logger.Debugf( + "discovered service type %s from package %s", + obj.Name(), + obj.Pkg().Path(), + ) + + success := false + defer func() { + if !success { + generator.logger.Errorf( + "package %s: type %s: service code generation failed", + obj.Pkg().Path(), + obj.Name(), + ) + } + }() + + // Collect service information. + info := generator.collector.Service(obj).Collect() + if info == nil { + return + } + + if info.IsEmpty() { + if !info.HasInternalMethods { + generator.logger.Infof( + "package %s: type %s: service has no valid exported methods, skipping", + obj.Pkg().Path(), + obj.Name(), + ) + } + success = true + return + } + + // Check for standard filename collisions. + filename := generator.renderer.ServiceFile(info.Name) + switch filename { + case generator.renderer.ModelsFile(): + generator.logger.Errorf( + "package %s: type %s: service filename collides with models filename; please rename the type or choose a different filename for models", + obj.Pkg().Path(), + obj.Name(), + ) + return + + case generator.renderer.IndexFile(): + if !generator.options.NoIndex { + generator.logger.Errorf( + "package %s: type %s: service filename collides with JS/TS index filename; please rename the type or choose a different filename for JS/TS indexes", + obj.Pkg().Path(), + obj.Name(), + ) + return + } + } + + // Check for upper/lower-case filename collisions. + path := filepath.Join(info.Imports.Self, filename) + if other, present := generator.serviceFiles.LoadOrStore(path, obj); present { + generator.logger.Errorf( + "package %s: type %s: service filename collides with filename for service %s; please avoid multiple services whose names differ only in case", + obj.Pkg().Path(), + obj.Name(), + other.(*types.TypeName).Name(), + ) + return + } + + // Create service file. + file, err := generator.creator.Create(path) + if err != nil { + generator.logger.Errorf("%v", err) + return + } + defer func() { + if err := file.Close(); err != nil { + generator.logger.Errorf("%v", err) + success = false + } + }() + + // Render service code. + err = generator.renderer.Service(file, info) + if err != nil { + generator.logger.Errorf("%v", err) + return + } + + success = true +} diff --git a/v3/internal/generator/testcases/aliases/bound_types.json b/v3/internal/generator/testcases/aliases/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/aliases/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/aliases/main.go b/v3/internal/generator/testcases/aliases/main.go new file mode 100644 index 000000000..f50d1634a --- /dev/null +++ b/v3/internal/generator/testcases/aliases/main.go @@ -0,0 +1,137 @@ +package main + +import ( + _ "embed" + "encoding" + "log" + + nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +// A nice type Alias. +type Alias = int + +// A class alias. +type AliasedPerson = Person + +// An empty struct alias. +type EmptyAliasStruct = struct{} + +// A struct alias. +// This should be rendered as a typedef or interface in every mode. +type AliasStruct = struct { + // A field with a comment. + Foo []int + Bar, Baz string `json:",omitempty"` // Definitely not Foo. + + Other OtherAliasStruct // A nested alias struct. +} + +// Another struct alias. +type OtherAliasStruct = struct { + NoMoreIdeas []rune +} + +// An empty struct. +type EmptyStruct struct{} + +// A non-generic struct containing an alias. +type Person struct { + Name string // The Person's name. + AliasedField Alias // A random alias field. +} + +// A generic struct containing an alias. +type GenericPerson[T any] struct { + Name T + AliasedField Alias +} + +// Another class alias, but ordered after its aliased class. +type StrangelyAliasedPerson = Person + +// A generic alias that forwards to a type parameter. +type GenericAlias[T any] = T + +// A generic alias that wraps a pointer type. +type GenericPtrAlias[T any] = *GenericAlias[T] + +// A generic alias that wraps a map. +type GenericMapAlias[T interface { + comparable + encoding.TextMarshaler +}, U any] = map[T]U + +// A generic alias that wraps a generic struct. +type GenericPersonAlias[T any] = GenericPerson[[]GenericPtrAlias[T]] + +// An alias that wraps a class through a non-typeparam alias. +type IndirectPersonAlias = GenericPersonAlias[bool] + +// An alias that wraps a class through a typeparam alias. +type TPIndirectPersonAlias = GenericAlias[GenericPerson[bool]] + +// A class whose fields have various aliased types. +type AliasGroup struct { + GAi GenericAlias[int] + GAP GenericAlias[GenericPerson[bool]] + GPAs GenericPtrAlias[[]string] + GPAP GenericPtrAlias[GenericPerson[[]int]] + GMA GenericMapAlias[struct{ encoding.TextMarshaler }, float32] + GPA GenericPersonAlias[bool] + IPA IndirectPersonAlias + TPIPA TPIndirectPersonAlias +} + +// Get someone. +func (GreetService) Get(aliasValue Alias) Person { + return Person{"hello", aliasValue} +} + +// Get someone quite different. +func (GreetService) GetButDifferent() GenericPerson[bool] { + return GenericPerson[bool]{true, 13} +} + +// Apparently, aliases are all the rage right now. +func (GreetService) GetButAliased(p AliasedPerson) StrangelyAliasedPerson { + return p +} + +func (GreetService) GetButForeignPrivateAlias() (_ nobindingshere.PrivatePerson) { + return +} + +func (GreetService) GetButGenericAliases() (_ AliasGroup) { + return +} + +// Greet a lot of unusual things. +func (GreetService) Greet(EmptyAliasStruct, EmptyStruct) AliasStruct { + return AliasStruct{} +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/complex_expressions/bound_types.json b/v3/internal/generator/testcases/complex_expressions/bound_types.json new file mode 100644 index 000000000..b00766ba0 --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/bound_types.json @@ -0,0 +1,14 @@ +[ + ".Service1", + ".Service2", + ".Service3", + ".Service4", + ".Service5", + ".Service6", + "/config.Service7", + "/config.Service8", + "/config.Service9", + "/config.Service10", + "/config.Service11", + "/config.Service12" +] diff --git a/v3/internal/generator/testcases/complex_expressions/config/config.go b/v3/internal/generator/testcases/complex_expressions/config/config.go new file mode 100644 index 000000000..f5f40dd86 --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/config/config.go @@ -0,0 +1,82 @@ +package config + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Service7 struct{} +type Service8 struct{} +type Service9 struct{} +type Service10 struct{} +type Service11 struct{} +type Service12 struct{} + +func (*Service7) TestMethod() {} + +func (*Service9) TestMethod2() {} + +func NewService7() application.Service { + return application.NewService(new(Service7)) +} + +func NewService8() (result application.Service) { + result = application.NewService(&Service8{}) + return +} + +type ServiceProvider struct { + AService application.Service + *ProviderWithMethod + HeresAnotherOne application.Service +} + +type ProviderWithMethod struct { + OtherService any +} + +func (pm *ProviderWithMethod) Init() { + pm.OtherService = application.NewService(&Service10{}) +} + +var Services []application.Service + +func init() { + var ourServices = []application.Service{ + NewService7(), + NewService8(), + } + + Services = make([]application.Service, len(ourServices)) + + for i, el := range ourServices { + Services[len(ourServices)-i] = el + } +} + +func MoreServices() ServiceProvider { + var provider ServiceProvider + + provider.AService = application.NewService(&Service9{}) + provider.ProviderWithMethod = new(ProviderWithMethod) + + return provider +} + +type ProviderInitialiser interface { + InitProvider(provider any) +} + +type internalProviderInitialiser struct{} + +func NewProviderInitialiser() ProviderInitialiser { + return internalProviderInitialiser{} +} + +func (internalProviderInitialiser) InitProvider(provider any) { + switch p := provider.(type) { + case *ServiceProvider: + p.HeresAnotherOne = application.NewService(&Service11{}) + default: + if anyp, ok := p.(*any); ok { + *anyp = application.NewService(&Service12{}) + } + } +} diff --git a/v3/internal/generator/testcases/complex_expressions/main.go b/v3/internal/generator/testcases/complex_expressions/main.go new file mode 100644 index 000000000..eca22794e --- /dev/null +++ b/v3/internal/generator/testcases/complex_expressions/main.go @@ -0,0 +1,66 @@ +package main + +import ( + _ "embed" + "log" + "slices" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service1 struct{} +type Service2 struct{} +type Service3 struct{} +type Service4 struct{} +type Service5 struct{} +type Service6 struct{} + +var GlobalServices []application.Service + +func main() { + services := []application.Service{ + application.NewService(&Service1{}), + } + + services = append(services, application.NewService(&Service2{}), application.Service{}, application.Service{}) + services[2] = application.NewService(&Service3{}) + + var options = application.Options{ + Services: config.Services, + } + + provider := config.MoreServices() + provider.Init() + + pinit := config.NewProviderInitialiser() + pinit.InitProvider(&provider) + // Method resolution should work here just like above. + config.NewProviderInitialiser().InitProvider(&services[3]) + + copy(options.Services, []application.Service{application.NewService(&Service4{}), provider.HeresAnotherOne, provider.OtherService.(application.Service)}) + (options.Services) = append(options.Services, slices.Insert(services, 1, GlobalServices[2:]...)...) + + app := application.New(options) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} + +func init() { + var global = make([]application.Service, 4) + + global[0] = application.NewService(&Service5{}) + global = slices.Replace(global, 1, 4, + application.NewService(&Service6{}), + config.MoreServices().AService, + ) + + GlobalServices = slices.Clip(global) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/bound_types.json b/v3/internal/generator/testcases/complex_instantiations/bound_types.json new file mode 100644 index 000000000..663519c0c --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/bound_types.json @@ -0,0 +1,18 @@ +[ + ".Service1", + ".Service2", + ".Service3", + ".Service4", + ".Service5", + ".Service6", + ".Service7", + ".Service8", + ".Service9", + ".Service10", + ".Service11", + ".Service12", + ".Service13", + ".Service14", + ".Service15", + "/other.Service16" +] diff --git a/v3/internal/generator/testcases/complex_instantiations/factory.go b/v3/internal/generator/testcases/complex_instantiations/factory.go new file mode 100644 index 000000000..854af6967 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/factory.go @@ -0,0 +1,21 @@ +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Factory[T any, U any] struct { + simpleFactory[T] +} + +func NewFactory[T any, U any]() *Factory[T, U] { + return &Factory[T, U]{} +} + +func (*Factory[T, U]) GetU() application.Service { + return application.NewService(new(U)) +} + +type simpleFactory[T any] struct{} + +func (simpleFactory[U]) Get() application.Service { + return application.NewService(new(U)) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/funcs.go b/v3/internal/generator/testcases/complex_instantiations/funcs.go new file mode 100644 index 000000000..7aea0e7e0 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/funcs.go @@ -0,0 +1,14 @@ +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func ServiceInitialiser[T any]() func(*T) application.Service { + return application.NewService[T] +} + +func CustomNewServices[T any, U any]() []application.Service { + return []application.Service{ + application.NewService(new(T)), + application.NewService(new(U)), + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/main.go b/v3/internal/generator/testcases/complex_instantiations/main.go new file mode 100644 index 000000000..3b6a0fb6c --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/main.go @@ -0,0 +1,59 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/complex_instantiations/other" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service1 struct{} +type Service2 struct{} +type Service3 struct{} +type Service4 struct{} +type Service5 struct{} +type Service6 struct{} +type Service7 struct{} +type Service8 struct{} +type Service9 struct{} +type Service10 struct{} +type Service11 struct{} +type Service12 struct{} +type Service13 struct{} +type Service14 struct{} +type Service15 struct{} + +type SimplifiedFactory[T any] = Factory[T, Service15] + +func main() { + factory := NewFactory[Service1, Service2]() + otherFactory := other.NewFactory[Service3, Service4]() + + app := application.New(application.Options{ + Services: append(append( + []application.Service{ + factory.Get(), + factory.GetU(), + otherFactory.Get(), + otherFactory.GetU(), + application.NewService(&Service5{}), + ServiceInitialiser[Service6]()(&Service6{}), + other.CustomNewService(Service7{}), + other.ServiceInitialiser[Service8]()(&Service8{}), + application.NewServiceWithOptions(&Service13{}, application.ServiceOptions{Name: "custom name"}), + SimplifiedFactory[Service14]{}.Get(), + other.LocalService, + }, + CustomNewServices[Service9, Service10]()...), + other.CustomNewServices[Service11, Service12]()...), + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/factory.go b/v3/internal/generator/testcases/complex_instantiations/other/factory.go new file mode 100644 index 000000000..d8e3aadb7 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/factory.go @@ -0,0 +1,21 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Factory[T any, U any] struct { + simpleFactory[T] +} + +func NewFactory[T any, U any]() *Factory[T, U] { + return &Factory[T, U]{} +} + +func (*Factory[T, U]) GetU() application.Service { + return application.NewService(new(U)) +} + +type simpleFactory[T any] struct{} + +func (simpleFactory[U]) Get() application.Service { + return application.NewService(new(U)) +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/funcs.go b/v3/internal/generator/testcases/complex_instantiations/other/funcs.go new file mode 100644 index 000000000..daf1ad066 --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/funcs.go @@ -0,0 +1,18 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +func CustomNewService[T any](srv T) application.Service { + return application.NewService(&srv) +} + +func ServiceInitialiser[T any]() func(*T) application.Service { + return application.NewService[T] +} + +func CustomNewServices[T any, U any]() []application.Service { + return []application.Service{ + application.NewService(new(T)), + application.NewService(new(U)), + } +} diff --git a/v3/internal/generator/testcases/complex_instantiations/other/local.go b/v3/internal/generator/testcases/complex_instantiations/other/local.go new file mode 100644 index 000000000..f08abb26a --- /dev/null +++ b/v3/internal/generator/testcases/complex_instantiations/other/local.go @@ -0,0 +1,7 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +type Service16 int + +var LocalService = application.NewService(new(Service16)) diff --git a/v3/internal/generator/testcases/complex_json/bound_types.json b/v3/internal/generator/testcases/complex_json/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/complex_json/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/complex_json/main.go b/v3/internal/generator/testcases/complex_json/main.go new file mode 100644 index 000000000..87e57912a --- /dev/null +++ b/v3/internal/generator/testcases/complex_json/main.go @@ -0,0 +1,124 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) + +// GreetService is great +type GreetService struct{} + +type Embedded1 struct { + // Friends should be shadowed in Person by a field of lesser depth + Friends int + + // Vanish should be omitted from Person because there is another field with same depth and no tag + Vanish float32 + + // StillThere should be shadowed in Person by other field with same depth and a json tag + StillThere string + + // embedded4 should effectively appear as an embedded field + embedded4 + + // unexported should be invisible + unexported bool +} + +type Embedded2 struct { + // Vanish should be omitted from Person because there is another field with same depth and no tag + Vanish bool + + // StillThereButRenamed should shadow in Person the other field with same depth and no json tag + StillThereButRenamed *Embedded3 `json:"StillThere"` +} + +type Embedded3 string + +// Person represents a person +type Person struct { + // Titles is optional in JSON + Titles []Title `json:",omitzero"` + + // Names has a + // multiline comment + Names []string + + // Partner has a custom and complex JSON key + Partner *Person `json:"the person's partner โค๏ธ"` + Friends []*Person + + Embedded1 + Embedded2 + + // UselessMap is invisible to JSON + UselessMap map[int]bool `json:"-"` + + // StrangeNumber maps to "-" + StrangeNumber float32 `json:"-,"` + + // Embedded3 should appear with key "Embedded3" + Embedded3 + + // StrangerNumber is serialized as a string + StrangerNumber int `json:",string"` + // StrangestString is optional and serialized as a JSON string + StrangestString string `json:",omitempty,string"` + // StringStrangest is serialized as a JSON string and optional + StringStrangest string `json:",string,omitempty"` + + // unexportedToo should be invisible even with a json tag + unexportedToo bool `json:"Unexported"` + + // embedded4 should be optional and appear with key "emb4" + embedded4 `json:"emb4,omitempty"` +} + +type embedded4 struct { + // NamingThingsIsHard is a law of programming + NamingThingsIsHard bool `json:",string"` + + // Friends should not be shadowed in Person as embedded4 is not embedded + // from encoding/json's point of view; + // however, it should be shadowed in Embedded1 + Friends bool + + // Embedded string should be invisible because it's unexported + string +} + +// Greet does XYZ +func (*GreetService) Greet(person Person, emb Embedded1) string { + return "" +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/complex_method/bound_types.json b/v3/internal/generator/testcases/complex_method/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/complex_method/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/complex_method/main.go b/v3/internal/generator/testcases/complex_method/main.go new file mode 100644 index 000000000..23ea29dee --- /dev/null +++ b/v3/internal/generator/testcases/complex_method/main.go @@ -0,0 +1,43 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct{} + +// Person represents a person +type Person struct { + Name string +} + +// Greet does XYZ +// It has a multiline doc comment +// The comment has even some */ traps!! +func (*GreetService) Greet(str string, people []Person, _ struct { + AnotherCount int + AnotherOne *Person +}, assoc map[int]*bool, _ []*float32, other ...string) (person Person, _ any, err1 error, _ []int, err error) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/cyclic_imports/bound_types.json b/v3/internal/generator/testcases/cyclic_imports/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_imports/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/cyclic_imports/main.go b/v3/internal/generator/testcases/cyclic_imports/main.go new file mode 100644 index 000000000..58feb98a1 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_imports/main.go @@ -0,0 +1,55 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +type StructA struct { + B *structB +} + +type structB struct { + A *StructA +} + +type StructC struct { + D structD +} + +type structD struct { + E StructE +} + +type StructE struct{} + +// Make a cycle. +func (GreetService) MakeCycles() (_ StructA, _ StructC) { + return +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/cyclic_types/bound_types.json b/v3/internal/generator/testcases/cyclic_types/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_types/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/cyclic_types/main.go b/v3/internal/generator/testcases/cyclic_types/main.go new file mode 100644 index 000000000..a5a9f5b46 --- /dev/null +++ b/v3/internal/generator/testcases/cyclic_types/main.go @@ -0,0 +1,46 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +type Cyclic []map[string]Alias + +type Alias = *Cyclic + +type GenericCyclic[T any] []struct { + X *GenericCyclic[T] + Y []T +} + +// Make a cycle. +func (GreetService) MakeCycles() (_ Cyclic, _ GenericCyclic[GenericCyclic[int]]) { + return +} + +func NewGreetService() application.Service { + return application.NewService(new(GreetService)) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/directives/bound_types.json b/v3/internal/generator/testcases/directives/bound_types.json new file mode 100644 index 000000000..62c02c0ca --- /dev/null +++ b/v3/internal/generator/testcases/directives/bound_types.json @@ -0,0 +1,5 @@ +[ + ".InternalService", + ".Service", + ".unexportedService" +] diff --git a/v3/internal/generator/testcases/directives/includes.go b/v3/internal/generator/testcases/directives/includes.go new file mode 100644 index 000000000..669d5305c --- /dev/null +++ b/v3/internal/generator/testcases/directives/includes.go @@ -0,0 +1,11 @@ +//wails:include js/test.js +//wails:include **:js/test_all.js +//wails:include *c:js/test_c.js +//wails:include *i:js/test_i.js +//wails:include j*:js/test_j.js +//wails:include jc:js/test_jc.js +//wails:include ji:js/test_ji.js +//wails:include t*:js/test_t.ts +//wails:include tc:js/test_tc.ts +//wails:include ti:js/test_ti.ts +package main diff --git a/v3/internal/generator/testcases/directives/internal.go b/v3/internal/generator/testcases/directives/internal.go new file mode 100644 index 000000000..86f06e02d --- /dev/null +++ b/v3/internal/generator/testcases/directives/internal.go @@ -0,0 +1,15 @@ +package main + +// An exported but internal model. +// +//wails:internal +type InternalModel struct { + Field string +} + +// An exported but internal service. +// +//wails:internal +type InternalService struct{} + +func (InternalService) Method(InternalModel) {} diff --git a/v3/internal/generator/testcases/directives/js/test.js b/v3/internal/generator/testcases/directives/js/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testcases/directives/js/test_all.js b/v3/internal/generator/testcases/directives/js/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testcases/directives/js/test_c.js b/v3/internal/generator/testcases/directives/js/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_i.js b/v3/internal/generator/testcases/directives/js/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testcases/directives/js/test_j.js b/v3/internal/generator/testcases/directives/js/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testcases/directives/js/test_jc.js b/v3/internal/generator/testcases/directives/js/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_ji.js b/v3/internal/generator/testcases/directives/js/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testcases/directives/js/test_t.ts b/v3/internal/generator/testcases/directives/js/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testcases/directives/js/test_tc.ts b/v3/internal/generator/testcases/directives/js/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testcases/directives/js/test_ti.ts b/v3/internal/generator/testcases/directives/js/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testcases/directives/js/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testcases/directives/main.go b/v3/internal/generator/testcases/directives/main.go new file mode 100644 index 000000000..ee204027a --- /dev/null +++ b/v3/internal/generator/testcases/directives/main.go @@ -0,0 +1,60 @@ +//wails:inject console.log("Hello everywhere!"); +//wails:inject **:console.log("Hello everywhere again!"); +//wails:inject *c:console.log("Hello Classes!"); +//wails:inject *i:console.log("Hello Interfaces!"); +//wails:inject j*:console.log("Hello JS!"); +//wails:inject jc:console.log("Hello JS Classes!"); +//wails:inject ji:console.log("Hello JS Interfaces!"); +//wails:inject t*:console.log("Hello TS!"); +//wails:inject tc:console.log("Hello TS Classes!"); +//wails:inject ti:console.log("Hello TS Interfaces!"); +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type IgnoredType struct { + Field int +} + +//wails:inject j*:/** +//wails:inject j*: * @param {string} arg +//wails:inject j*: * @returns {Promise} +//wails:inject j*: */ +//wails:inject j*:export async function CustomMethod(arg) { +//wails:inject t*:export async function CustomMethod(arg: string): Promise { +//wails:inject await InternalMethod("Hello " + arg + "!"); +//wails:inject } +type Service struct{} + +func (*Service) VisibleMethod(otherpackage.Dummy) {} + +//wails:ignore +func (*Service) IgnoredMethod(IgnoredType) {} + +//wails:internal +func (*Service) InternalMethod(string) {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + application.NewService(&unexportedService{}), + application.NewService(&InternalService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/directives/otherpackage/dummy.go b/v3/internal/generator/testcases/directives/otherpackage/dummy.go new file mode 100644 index 000000000..e8f688f05 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/dummy.go @@ -0,0 +1,5 @@ +//wails:include jc:js/*_j*.js +//wails:include tc:js/*_t*.ts +package otherpackage + +type Dummy struct{} diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js b/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js b/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts b/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts b/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testcases/directives/otherpackage/js/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testcases/directives/unexported.go b/v3/internal/generator/testcases/directives/unexported.go new file mode 100644 index 000000000..56708c5ae --- /dev/null +++ b/v3/internal/generator/testcases/directives/unexported.go @@ -0,0 +1,11 @@ +package main + +// An unexported model. +type unexportedModel struct { + Field string +} + +// An unexported service. +type unexportedService struct{} + +func (unexportedService) Method(unexportedModel) {} diff --git a/v3/internal/generator/testcases/embedded_interface/bound_types.json b/v3/internal/generator/testcases/embedded_interface/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/embedded_interface/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/embedded_interface/main.go b/v3/internal/generator/testcases/embedded_interface/main.go new file mode 100644 index 000000000..2f65d6519 --- /dev/null +++ b/v3/internal/generator/testcases/embedded_interface/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + AnInterface +} + +type AnInterface interface { + // Comment 1. + Method1() + + Method2() // Comment 2. + + // Comment 3a. + Method3() // Comment 3b. + + interface { + // Comment 4. + Method4() + } + + InterfaceAlias +} + +type InterfaceAlias = interface { + // Comment 5. + Method5() +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum/bound_types.json b/v3/internal/generator/testcases/enum/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/enum/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/enum/main.go b/v3/internal/generator/testcases/enum/main.go new file mode 100644 index 000000000..22603ce37 --- /dev/null +++ b/v3/internal/generator/testcases/enum/main.go @@ -0,0 +1,74 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) + +// Age is an integer with some predefined values +type Age = int + +const ( + NewBorn Age = 0 + Teenager Age = 12 + YoungAdult Age = 18 + + // Oh no, some grey hair! + MiddleAged Age = 50 + Mathusalem Age = 1000 // Unbelievable! +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person represents a person +type Person struct { + Title Title + Name string + Age Age +} + +// Greet does XYZ +func (*GreetService) Greet(name string, title Title) string { + return "Hello " + string(title) + " " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json b/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/enum_from_imported_package/main.go b/v3/internal/generator/testcases/enum_from_imported_package/main.go new file mode 100644 index 000000000..f0348eb5c --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/main.go @@ -0,0 +1,38 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet does XYZ +func (*GreetService) Greet(name string, title services.Title) string { + return "Hello " + title.String() + " " + name +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/enum_from_imported_package/services/other.go b/v3/internal/generator/testcases/enum_from_imported_package/services/other.go new file mode 100644 index 000000000..5d15f239c --- /dev/null +++ b/v3/internal/generator/testcases/enum_from_imported_package/services/other.go @@ -0,0 +1,16 @@ +package services + +type Title string + +func (t Title) String() string { + return string(t) +} + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) diff --git a/v3/internal/generator/testcases/function_from_imported_package/bound_types.json b/v3/internal/generator/testcases/function_from_imported_package/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/function_from_imported_package/main.go b/v3/internal/generator/testcases/function_from_imported_package/main.go new file mode 100644 index 000000000..03e5b209f --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/main.go @@ -0,0 +1,51 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person is a person +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + services.NewOtherService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_from_imported_package/services/other.go b/v3/internal/generator/testcases/function_from_imported_package/services/other.go new file mode 100644 index 000000000..4ac6e6efd --- /dev/null +++ b/v3/internal/generator/testcases/function_from_imported_package/services/other.go @@ -0,0 +1,28 @@ +package services + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json b/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json new file mode 100644 index 000000000..7f9f0ea61 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services/other.OtherService" +] diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/main.go b/v3/internal/generator/testcases/function_from_nested_imported_package/main.go new file mode 100644 index 000000000..820fc4f83 --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *other.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + other.NewOtherService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go b/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go new file mode 100644 index 000000000..fa12f63ce --- /dev/null +++ b/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/other.go @@ -0,0 +1,28 @@ +package other + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testcases/function_multiple_files/bound_types.json b/v3/internal/generator/testcases/function_multiple_files/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_multiple_files/greet.go b/v3/internal/generator/testcases/function_multiple_files/greet.go new file mode 100644 index 000000000..b0153f22f --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/greet.go @@ -0,0 +1,20 @@ +package main + +import ( + _ "embed" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} diff --git a/v3/internal/generator/testcases/function_multiple_files/main.go b/v3/internal/generator/testcases/function_multiple_files/main.go new file mode 100644 index 000000000..240168530 --- /dev/null +++ b/v3/internal/generator/testcases/function_multiple_files/main.go @@ -0,0 +1,25 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single/bound_types.json b/v3/internal/generator/testcases/function_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single/main.go b/v3/internal/generator/testcases/function_single/main.go new file mode 100644 index 000000000..738505455 --- /dev/null +++ b/v3/internal/generator/testcases/function_single/main.go @@ -0,0 +1,40 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single_context/bound_types.json b/v3/internal/generator/testcases/function_single_context/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_context/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single_context/main.go b/v3/internal/generator/testcases/function_single_context/main.go new file mode 100644 index 000000000..1a785ddba --- /dev/null +++ b/v3/internal/generator/testcases/function_single_context/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// Greet someone +func (*GreetService) GreetWithContext(ctx context.Context, name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/function_single_internal/bound_types.json b/v3/internal/generator/testcases/function_single_internal/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_internal/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/function_single_internal/main.go b/v3/internal/generator/testcases/function_single_internal/main.go new file mode 100644 index 000000000..f66dd3649 --- /dev/null +++ b/v3/internal/generator/testcases/function_single_internal/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + _ "embed" + "log" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// Debugging name +func (*GreetService) ServiceName() string { + return "GreetService" +} + +// Lifecycle +func (*GreetService) ServiceStartup(context.Context, application.ServiceOptions) error { + return nil +} + +// Lifecycle +func (*GreetService) ServiceShutdown() error { + return nil +} + +// Serve some routes +func (*GreetService) ServeHTTP(http.ResponseWriter, *http.Request) { +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + NewGreetService(), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/map_keys/bound_types.json b/v3/internal/generator/testcases/map_keys/bound_types.json new file mode 100644 index 000000000..1a27d889e --- /dev/null +++ b/v3/internal/generator/testcases/map_keys/bound_types.json @@ -0,0 +1,3 @@ +[ + ".Service" +] diff --git a/v3/internal/generator/testcases/map_keys/main.go b/v3/internal/generator/testcases/map_keys/main.go new file mode 100644 index 000000000..b5d331bad --- /dev/null +++ b/v3/internal/generator/testcases/map_keys/main.go @@ -0,0 +1,307 @@ +package main + +import ( + _ "embed" + "encoding" + "encoding/json" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service struct{} + +type NonTextMarshaler struct{} + +type ValueTextMarshaler struct{} + +func (ValueTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type PointerTextMarshaler struct{} + +func (*PointerTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type JsonTextMarshaler struct{} + +func (JsonTextMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (JsonTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +type CustomInterface interface { + MarshalText() ([]byte, error) +} + +type EmbeddedInterface interface { + encoding.TextMarshaler +} + +type EmbeddedInterfaces interface { + json.Marshaler + encoding.TextMarshaler +} + +type BasicConstraint interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~string +} + +type BadTildeConstraint interface { + int | ~struct{} | string +} + +type GoodTildeConstraint interface { + int | ~struct{} | string + MarshalText() ([]byte, error) +} + +type NonBasicConstraint interface { + ValueTextMarshaler | *PointerTextMarshaler +} + +type PointableConstraint interface { + ValueTextMarshaler | PointerTextMarshaler +} + +type MixedConstraint interface { + uint | ~string | ValueTextMarshaler | *PointerTextMarshaler +} + +type InterfaceConstraint interface { + comparable + encoding.TextMarshaler +} + +type PointerConstraint[T comparable] interface { + *T + encoding.TextMarshaler +} + +type EmbeddedValue struct{ ValueTextMarshaler } +type EmbeddedValuePtr struct{ *ValueTextMarshaler } +type EmbeddedPointer struct{ PointerTextMarshaler } +type EmbeddedPointerPtr struct{ *PointerTextMarshaler } + +type EmbeddedCustomInterface struct{ CustomInterface } +type EmbeddedOriginalInterface struct{ encoding.TextMarshaler } + +type WrongType bool +type WrongAlias = bool +type StringType string +type StringAlias = string +type IntType int +type IntAlias = int + +type ValueType ValueTextMarshaler +type ValuePtrType *ValueTextMarshaler +type ValueAlias = ValueTextMarshaler +type ValuePtrAlias = *ValueTextMarshaler + +type PointerType PointerTextMarshaler +type PointerPtrType *PointerTextMarshaler +type PointerAlias = PointerTextMarshaler +type PointerPtrAlias = *PointerTextMarshaler + +type InterfaceType encoding.TextMarshaler +type InterfacePtrType *encoding.TextMarshaler +type InterfaceAlias = encoding.TextMarshaler +type InterfacePtrAlias = *encoding.TextMarshaler + +type ComparableCstrAlias[R comparable] = R +type ComparableCstrPtrAlias[R comparable] = *R +type BasicCstrAlias[S BasicConstraint] = S +type BasicCstrPtrAlias[S BasicConstraint] = *S +type BadTildeCstrAlias[T BadTildeConstraint] = T +type BadTildeCstrPtrAlias[T BadTildeConstraint] = *T +type GoodTildeCstrAlias[U GoodTildeConstraint] = U +type GoodTildeCstrPtrAlias[U GoodTildeConstraint] = *U +type NonBasicCstrAlias[V NonBasicConstraint] = V +type NonBasicCstrPtrAlias[V NonBasicConstraint] = *V +type PointableCstrAlias[W PointableConstraint] = W +type PointableCstrPtrAlias[W PointableConstraint] = *W +type MixedCstrAlias[X MixedConstraint] = X +type MixedCstrPtrAlias[X MixedConstraint] = *X +type InterfaceCstrAlias[Y InterfaceConstraint] = Y +type InterfaceCstrPtrAlias[Y InterfaceConstraint] = *Y +type PointerCstrAlias[R comparable, Z PointerConstraint[R]] = Z +type PointerCstrPtrAlias[R comparable, Z PointerConstraint[R]] = *Z + +type Maps[R comparable, S BasicConstraint, T BadTildeConstraint, U GoodTildeConstraint, V NonBasicConstraint, W PointableConstraint, X MixedConstraint, Y InterfaceConstraint, Z PointerConstraint[R]] struct { + Bool map[bool]int // Reject + Int map[int]int // Accept + Uint map[uint]int // Accept + Float map[float32]int // Reject + Complex map[complex64]int // Reject + Byte map[byte]int // Accept + Rune map[rune]int // Accept + String map[string]int // Accept + + IntPtr map[*int]int // Reject + UintPtr map[*uint]int // Reject + FloatPtr map[*float32]int // Reject + ComplexPtr map[*complex64]int // Reject + StringPtr map[*string]int // Reject + + NTM map[NonTextMarshaler]int // Reject + NTMPtr map[*NonTextMarshaler]int // Reject + VTM map[ValueTextMarshaler]int // Accept + VTMPtr map[*ValueTextMarshaler]int // Accept + PTM map[PointerTextMarshaler]int // Reject + PTMPtr map[*PointerTextMarshaler]int // Accept + JTM map[JsonTextMarshaler]int // Accept, hide + JTMPtr map[*JsonTextMarshaler]int // Accept, hide + + A map[any]int // Reject + APtr map[*any]int // Reject + TM map[encoding.TextMarshaler]int // Accept, hide + TMPtr map[*encoding.TextMarshaler]int // Reject + CI map[CustomInterface]int // Accept, hide + CIPtr map[*CustomInterface]int // Reject + EI map[EmbeddedInterface]int // Accept, hide + EIPtr map[*EmbeddedInterface]int // Reject + + EV map[EmbeddedValue]int // Accept + EVPtr map[*EmbeddedValue]int // Accept + EVP map[EmbeddedValuePtr]int // Accept + EVPPtr map[*EmbeddedValuePtr]int // Accept + EP map[EmbeddedPointer]int // Reject + EPPtr map[*EmbeddedPointer]int // Accept + EPP map[EmbeddedPointerPtr]int // Accept + EPPPtr map[*EmbeddedPointerPtr]int // Accept + + ECI map[EmbeddedCustomInterface]int // Accept + ECIPtr map[*EmbeddedCustomInterface]int // Accept + EOI map[EmbeddedOriginalInterface]int // Accept + EOIPtr map[*EmbeddedOriginalInterface]int // Accept + + WT map[WrongType]int // Reject + WA map[WrongAlias]int // Reject + ST map[StringType]int // Accept + SA map[StringAlias]int // Accept + IntT map[IntType]int // Accept + IntA map[IntAlias]int // Accept + + VT map[ValueType]int // Reject + VTPtr map[*ValueType]int // Reject + VPT map[ValuePtrType]int // Reject + VPTPtr map[*ValuePtrType]int // Reject + VA map[ValueAlias]int // Accept + VAPtr map[*ValueAlias]int // Accept + VPA map[ValuePtrAlias]int // Accept, hide + VPAPtr map[*ValuePtrAlias]int // Reject + + PT map[PointerType]int // Reject + PTPtr map[*PointerType]int // Reject + PPT map[PointerPtrType]int // Reject + PPTPtr map[*PointerPtrType]int // Reject + PA map[PointerAlias]int // Reject + PAPtr map[*PointerAlias]int // Accept + PPA map[PointerPtrAlias]int // Accept, hide + PPAPtr map[*PointerPtrAlias]int // Reject + + IT map[InterfaceType]int // Accept, hide + ITPtr map[*InterfaceType]int // Reject + IPT map[InterfacePtrType]int // Reject + IPTPtr map[*InterfacePtrType]int // Reject + IA map[InterfaceAlias]int // Accept, hide + IAPtr map[*InterfaceAlias]int // Reject + IPA map[InterfacePtrAlias]int // Reject + IPAPtr map[*InterfacePtrAlias]int // Reject + + TPR map[R]int // Soft reject + TPRPtr map[*R]int // Soft reject + TPS map[S]int // Accept, hide + TPSPtr map[*S]int // Soft reject + TPT map[T]int // Soft reject + TPTPtr map[*T]int // Soft reject + TPU map[U]int // Accept, hide + TPUPtr map[*U]int // Soft reject + TPV map[V]int // Accept, hide + TPVPtr map[*V]int // Soft reject + TPW map[W]int // Soft reject + TPWPtr map[*W]int // Accept, hide + TPX map[X]int // Accept, hide + TPXPtr map[*X]int // Soft reject + TPY map[Y]int // Accept, hide + TPYPtr map[*Y]int // Soft reject + TPZ map[Z]int // Accept, hide + TPZPtr map[*Z]int // Soft reject + + GAR map[ComparableCstrAlias[R]]int // Soft reject + GARPtr map[ComparableCstrPtrAlias[R]]int // Soft reject + GAS map[BasicCstrAlias[S]]int // Accept, hide + GASPtr map[BasicCstrPtrAlias[S]]int // Soft reject + GAT map[BadTildeCstrAlias[T]]int // Soft reject + GATPtr map[BadTildeCstrPtrAlias[T]]int // Soft reject + GAU map[GoodTildeCstrAlias[U]]int // Accept, hide + GAUPtr map[GoodTildeCstrPtrAlias[U]]int // Soft reject + GAV map[NonBasicCstrAlias[V]]int // Accept, hide + GAVPtr map[NonBasicCstrPtrAlias[V]]int // Soft reject + GAW map[PointableCstrAlias[W]]int // Soft reject + GAWPtr map[PointableCstrPtrAlias[W]]int // Accept, hide + GAX map[MixedCstrAlias[X]]int // Accept, hide + GAXPtr map[MixedCstrPtrAlias[X]]int // Soft reject + GAY map[InterfaceCstrAlias[Y]]int // Accept, hide + GAYPtr map[InterfaceCstrPtrAlias[Y]]int // Soft reject + GAZ map[PointerCstrAlias[R, Z]]int // Accept, hide + GAZPtr map[PointerCstrPtrAlias[R, Z]]int // Soft reject + + GACi map[ComparableCstrAlias[int]]int // Accept, hide + GACV map[ComparableCstrAlias[ValueTextMarshaler]]int // Accept + GACP map[ComparableCstrAlias[PointerTextMarshaler]]int // Reject + GACiPtr map[ComparableCstrPtrAlias[int]]int // Reject + GACVPtr map[ComparableCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GACPPtr map[ComparableCstrPtrAlias[PointerTextMarshaler]]int // Accept, hide + GABi map[BasicCstrAlias[int]]int // Accept, hide + GABs map[BasicCstrAlias[string]]int // Accept + GABiPtr map[BasicCstrPtrAlias[int]]int // Reject + GABT map[BadTildeCstrAlias[struct{}]]int // Reject + GABTPtr map[BadTildeCstrPtrAlias[struct{}]]int // Reject + GAGT map[GoodTildeCstrAlias[ValueTextMarshaler]]int // Accept + GAGTPtr map[GoodTildeCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GANBV map[NonBasicCstrAlias[ValueTextMarshaler]]int // Accept + GANBP map[NonBasicCstrAlias[*PointerTextMarshaler]]int // Accept, hide + GANBVPtr map[NonBasicCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GANBPPtr map[NonBasicCstrPtrAlias[*PointerTextMarshaler]]int // Reject + GAPlV1 map[PointableCstrAlias[ValueTextMarshaler]]int // Accept + GAPlV2 map[*PointableCstrAlias[ValueTextMarshaler]]int // Accept + GAPlP1 map[PointableCstrAlias[PointerTextMarshaler]]int // Reject + GAPlP2 map[*PointableCstrAlias[PointerTextMarshaler]]int // Accept + GAPlVPtr map[PointableCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAPlPPtr map[PointableCstrPtrAlias[PointerTextMarshaler]]int // Accept, hide + GAMi map[MixedCstrAlias[uint]]int // Accept, hide + GAMS map[MixedCstrAlias[StringType]]int // Accept + GAMV map[MixedCstrAlias[ValueTextMarshaler]]int // Accept + GAMSPtr map[MixedCstrPtrAlias[StringType]]int // Reject + GAMVPtr map[MixedCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAII map[InterfaceCstrAlias[encoding.TextMarshaler]]int // Accept, hide + GAIV map[InterfaceCstrAlias[ValueTextMarshaler]]int // Accept + GAIP map[InterfaceCstrAlias[*PointerTextMarshaler]]int // Accept, hide + GAIIPtr map[InterfaceCstrPtrAlias[encoding.TextMarshaler]]int // Reject + GAIVPtr map[InterfaceCstrPtrAlias[ValueTextMarshaler]]int // Accept, hide + GAIPPtr map[InterfaceCstrPtrAlias[*PointerTextMarshaler]]int // Reject + GAPrV map[PointerCstrAlias[ValueTextMarshaler, *ValueTextMarshaler]]int // Accept, hide + GAPrP map[PointerCstrAlias[PointerTextMarshaler, *PointerTextMarshaler]]int // Accept, hide + GAPrVPtr map[PointerCstrPtrAlias[ValueTextMarshaler, *ValueTextMarshaler]]int // Reject + GAPrPPtr map[PointerCstrPtrAlias[PointerTextMarshaler, *PointerTextMarshaler]]int // Reject +} + +func (*Service) Method() (_ Maps[PointerTextMarshaler, int, int, ValueTextMarshaler, *PointerTextMarshaler, ValueTextMarshaler, StringType, ValueTextMarshaler, *PointerTextMarshaler]) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/marshalers/bound_types.json b/v3/internal/generator/testcases/marshalers/bound_types.json new file mode 100644 index 000000000..1a27d889e --- /dev/null +++ b/v3/internal/generator/testcases/marshalers/bound_types.json @@ -0,0 +1,3 @@ +[ + ".Service" +] diff --git a/v3/internal/generator/testcases/marshalers/main.go b/v3/internal/generator/testcases/marshalers/main.go new file mode 100644 index 000000000..32c934e49 --- /dev/null +++ b/v3/internal/generator/testcases/marshalers/main.go @@ -0,0 +1,209 @@ +package main + +import ( + _ "embed" + "encoding" + "encoding/json" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Service struct{} + +// class {} +type NonMarshaler struct{} + +// any +type ValueJsonMarshaler struct{} + +func (ValueJsonMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } + +// any +type PointerJsonMarshaler struct{} + +func (*PointerJsonMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } + +// string +type ValueTextMarshaler struct{} + +func (ValueTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// string +type PointerTextMarshaler struct{} + +func (*PointerTextMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type ValueMarshaler struct{} + +func (ValueMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (ValueMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type PointerMarshaler struct{} + +func (*PointerMarshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (*PointerMarshaler) MarshalText() ([]byte, error) { return nil, nil } + +// any +type UnderlyingJsonMarshaler struct{ json.Marshaler } + +// string +type UnderlyingTextMarshaler struct{ encoding.TextMarshaler } + +// any +type UnderlyingMarshaler struct { + json.Marshaler + encoding.TextMarshaler +} + +type customJsonMarshaler interface { + MarshalJSON() ([]byte, error) +} + +type customTextMarshaler interface { + MarshalText() ([]byte, error) +} + +type customMarshaler interface { + MarshalJSON() ([]byte, error) + MarshalText() ([]byte, error) +} + +// struct{} +type AliasNonMarshaler = struct{} + +// any +type AliasJsonMarshaler = struct{ json.Marshaler } + +// string +type AliasTextMarshaler = struct{ encoding.TextMarshaler } + +// any +type AliasMarshaler = struct { + json.Marshaler + encoding.TextMarshaler +} + +// any +type ImplicitJsonMarshaler UnderlyingJsonMarshaler + +// string +type ImplicitTextMarshaler UnderlyingTextMarshaler + +// any +type ImplicitMarshaler UnderlyingMarshaler + +// string +type ImplicitNonJson UnderlyingMarshaler + +func (ImplicitNonJson) MarshalJSON() {} + +// any +type ImplicitNonText UnderlyingMarshaler + +func (ImplicitNonText) MarshalText() {} + +// class{ Marshaler, TextMarshaler } +type ImplicitNonMarshaler UnderlyingMarshaler + +func (ImplicitNonMarshaler) MarshalJSON() {} +func (ImplicitNonMarshaler) MarshalText() {} + +// any +type ImplicitJsonButText UnderlyingJsonMarshaler + +func (ImplicitJsonButText) MarshalText() ([]byte, error) { return nil, nil } + +// any +type ImplicitTextButJson UnderlyingTextMarshaler + +func (ImplicitTextButJson) MarshalJSON() ([]byte, error) { return nil, nil } + +type Data struct { + NM NonMarshaler + NMPtr *NonMarshaler // NonMarshaler | null + + VJM ValueJsonMarshaler + VJMPtr *ValueJsonMarshaler // ValueJsonMarshaler | null + PJM PointerJsonMarshaler + PJMPtr *PointerJsonMarshaler // PointerJsonMarshaler | null + + VTM ValueTextMarshaler + VTMPtr *ValueTextMarshaler // ValueTextMarshaler | null + PTM PointerTextMarshaler + PTMPtr *PointerTextMarshaler // PointerTextMarshaler | null + + VM ValueMarshaler + VMPtr *ValueMarshaler // ValueMarshaler | null + PM PointerMarshaler + PMPtr *PointerMarshaler // PointerMarshaler | null + + UJM UnderlyingJsonMarshaler + UJMPtr *UnderlyingJsonMarshaler // UnderlyingJsonMarshaler | null + UTM UnderlyingTextMarshaler + UTMPtr *UnderlyingTextMarshaler // UnderlyingTextMarshaler | null + UM UnderlyingMarshaler + UMPtr *UnderlyingMarshaler // UnderlyingMarshaler | null + + JM struct{ json.Marshaler } // any + JMPtr *struct{ json.Marshaler } // any | null + TM struct{ encoding.TextMarshaler } // string + TMPtr *struct{ encoding.TextMarshaler } // string | null + CJM struct{ customJsonMarshaler } // any + CJMPtr *struct{ customJsonMarshaler } // any | null + CTM struct{ customTextMarshaler } // string + CTMPtr *struct{ customTextMarshaler } // string | null + CM struct{ customMarshaler } // any + CMPtr *struct{ customMarshaler } // any | null + + ANM AliasNonMarshaler + ANMPtr *AliasNonMarshaler // AliasNonMarshaler | null + AJM AliasJsonMarshaler + AJMPtr *AliasJsonMarshaler // AliasJsonMarshaler | null + ATM AliasTextMarshaler + ATMPtr *AliasTextMarshaler // AliasTextMarshaler | null + AM AliasMarshaler + AMPtr *AliasMarshaler // AliasMarshaler | null + + ImJM ImplicitJsonMarshaler + ImJMPtr *ImplicitJsonMarshaler // ImplicitJsonMarshaler | null + ImTM ImplicitTextMarshaler + ImTMPtr *ImplicitTextMarshaler // ImplicitTextMarshaler | null + ImM ImplicitMarshaler + ImMPtr *ImplicitMarshaler // ImplicitMarshaler | null + + ImNJ ImplicitNonJson + ImNJPtr *ImplicitNonJson // ImplicitNonJson | null + ImNT ImplicitNonText + ImNTPtr *ImplicitNonText // ImplicitNonText | null + ImNM ImplicitNonMarshaler + ImNMPtr *ImplicitNonMarshaler // ImplicitNonMarshaler | null + + ImJbT ImplicitJsonButText + ImJbTPtr *ImplicitJsonButText // ImplicitJsonButText | null + ImTbJ ImplicitTextButJson + ImTbJPtr *ImplicitTextButJson // ImplicitTextButJson | null +} + +func (*Service) Method() (_ Data) { + return +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&Service{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go b/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go new file mode 100644 index 000000000..8d4ec6ba8 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/other/othermethods.go @@ -0,0 +1,11 @@ +package other + +// OtherMethods has another method, but through a private embedded type. +type OtherMethods struct { + otherMethodsImpl +} + +type otherMethodsImpl int + +// LikeThisOtherOne does nothing as well, but is different. +func (*otherMethodsImpl) LikeThisOtherOne() {} diff --git a/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go b/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go new file mode 100644 index 000000000..516c965b5 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/other/otherperson.go @@ -0,0 +1,10 @@ +package other + +// OtherPerson is like a person, but different. +type OtherPerson[T any] struct { + // They have a name as well. + Name string + + // But they may have many differences. + Differences []T +} diff --git a/v3/internal/generator/testcases/no_bindings_here/person.go b/v3/internal/generator/testcases/no_bindings_here/person.go new file mode 100644 index 000000000..b0b6f941d --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/person.go @@ -0,0 +1,26 @@ +package nobindingshere + +import "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + +// Person is not a number. +type Person struct { + // They have a name. + Name string + Friends [4]Impersonator // Exactly 4 sketchy friends. +} + +// Impersonator gets their fields from other people. +type Impersonator = other.OtherPerson[int] + +// HowDifferent is a curious kind of person +// that lets other people decide how they are different. +type HowDifferent[How any] other.OtherPerson[map[string]How] + +// PrivatePerson gets their fields from hidden sources. +type PrivatePerson = personImpl + +type personImpl struct { + // Nickname conceals a person's identity. + Nickname string + Person +} diff --git a/v3/internal/generator/testcases/no_bindings_here/somemethods.go b/v3/internal/generator/testcases/no_bindings_here/somemethods.go new file mode 100644 index 000000000..4b0602a64 --- /dev/null +++ b/v3/internal/generator/testcases/no_bindings_here/somemethods.go @@ -0,0 +1,13 @@ +package nobindingshere + +import "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + +// SomeMethods exports some methods. +type SomeMethods struct { + other.OtherMethods +} + +// LikeThisOne is an example method that does nothing. +func (SomeMethods) LikeThisOne() (_ Person, _ HowDifferent[bool], _ PrivatePerson) { + return +} diff --git a/v3/internal/generator/testcases/out_of_tree/bound_types.json b/v3/internal/generator/testcases/out_of_tree/bound_types.json new file mode 100644 index 000000000..c0714f177 --- /dev/null +++ b/v3/internal/generator/testcases/out_of_tree/bound_types.json @@ -0,0 +1,7 @@ +[ + ".GreetService", + ".EmbedService", + ".EmbedOther", + "/../no_bindings_here.SomeMethods", + "/../no_bindings_here/other.OtherMethods" +] diff --git a/v3/internal/generator/testcases/out_of_tree/main.go b/v3/internal/generator/testcases/out_of_tree/main.go new file mode 100644 index 000000000..c3dfbc9ee --- /dev/null +++ b/v3/internal/generator/testcases/out_of_tree/main.go @@ -0,0 +1,47 @@ +package main + +import ( + _ "embed" + "log" + + nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here" + "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService int + +// EmbedService is tricky. +type EmbedService struct { + nobindingshere.SomeMethods +} + +// EmbedOther is even trickier. +type EmbedOther struct { + other.OtherMethods +} + +// Greet someone +func (*GreetService) Greet(string) {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(new(GreetService)), + application.NewService(&EmbedService{}), + application.NewService(&EmbedOther{}), + application.NewService(&nobindingshere.SomeMethods{}), + application.NewService(&other.OtherMethods{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json new file mode 100644 index 000000000..be815a97e --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + ".OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple/main.go b/v3/internal/generator/testcases/struct_literal_multiple/main.go new file mode 100644 index 000000000..5e1081504 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple/main.go @@ -0,0 +1,41 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json new file mode 100644 index 000000000..be815a97e --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + ".OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go b/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go new file mode 100644 index 000000000..2a45396a7 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/greet.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "embed" +) + +type GreetService struct { + SomeVariable int + lowerCase string +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/main.go b/v3/internal/generator/testcases/struct_literal_multiple_files/main.go new file mode 100644 index 000000000..ae80a4cba --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/main.go @@ -0,0 +1,26 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_files/other.go b/v3/internal/generator/testcases/struct_literal_multiple_files/other.go new file mode 100644 index 000000000..ad5e661ef --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_files/other.go @@ -0,0 +1,7 @@ +package main + +type OtherService struct { + t int +} + +func (o *OtherService) Hello() {} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json b/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/main.go b/v3/internal/generator/testcases/struct_literal_multiple_other/main.go new file mode 100644 index 000000000..7b7a3a2ab --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + application.NewService(&services.OtherService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go b/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go new file mode 100644 index 000000000..55472595b --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_multiple_other/services/other.go @@ -0,0 +1,22 @@ +package services + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} diff --git a/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json b/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_non_pointer_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go b/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go new file mode 100644 index 000000000..e0fd8247d --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_non_pointer_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (GreetService) Greet(name string) string { + return "Hello " + name +} + +func (GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (GreetService) IntInIntOut(in int) int { + return in +} + +func (GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (GreetService) MapIntInt(in map[int]int) { +} + +func (GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (GreetService) MapIntIntPointer(in map[int]*int) { +} + +func (GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/struct_literal_single/bound_types.json b/v3/internal/generator/testcases/struct_literal_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/struct_literal_single/main.go b/v3/internal/generator/testcases/struct_literal_single/main.go new file mode 100644 index 000000000..945874a0b --- /dev/null +++ b/v3/internal/generator/testcases/struct_literal_single/main.go @@ -0,0 +1,205 @@ +package main + +import ( + _ "embed" + "log" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Person struct { + Name string + Parent *Person + Details struct { + Age int + Address struct { + Street string + } + } +} + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func (*GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (*GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (*GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (*GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (*GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (*GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (*GreetService) UIntPointerInAndOutput(in *uint) *uint { + return in +} + +func (*GreetService) UInt8PointerInAndOutput(in *uint8) *uint8 { + return in +} + +func (*GreetService) UInt16PointerInAndOutput(in *uint16) *uint16 { + return in +} + +func (*GreetService) UInt32PointerInAndOutput(in *uint32) *uint32 { + return in +} + +func (*GreetService) UInt64PointerInAndOutput(in *uint64) *uint64 { + return in +} + +func (*GreetService) IntPointerInAndOutput(in *int) *int { + return in +} + +func (*GreetService) Int8PointerInAndOutput(in *int8) *int8 { + return in +} + +func (*GreetService) Int16PointerInAndOutput(in *int16) *int16 { + return in +} + +func (*GreetService) Int32PointerInAndOutput(in *int32) *int32 { + return in +} + +func (*GreetService) Int64PointerInAndOutput(in *int64) *int64 { + return in +} + +func (*GreetService) IntInIntOut(in int) int { + return in +} + +func (*GreetService) Int8InIntOut(in int8) int8 { + return in +} +func (*GreetService) Int16InIntOut(in int16) int16 { + return in +} +func (*GreetService) Int32InIntOut(in int32) int32 { + return in +} +func (*GreetService) Int64InIntOut(in int64) int64 { + return in +} + +func (*GreetService) UIntInUIntOut(in uint) uint { + return in +} + +func (*GreetService) UInt8InUIntOut(in uint8) uint8 { + return in +} +func (*GreetService) UInt16InUIntOut(in uint16) uint16 { + return in +} +func (*GreetService) UInt32InUIntOut(in uint32) uint32 { + return in +} +func (*GreetService) UInt64InUIntOut(in uint64) uint64 { + return in +} + +func (*GreetService) Float32InFloat32Out(in float32) float32 { + return in +} + +func (*GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (*GreetService) PointerFloat32InFloat32Out(in *float32) *float32 { + return in +} + +func (*GreetService) PointerFloat64InFloat64Out(in *float64) *float64 { + return in +} + +func (*GreetService) BoolInBoolOut(in bool) bool { + return in +} + +func (*GreetService) PointerBoolInBoolOut(in *bool) *bool { + return in +} + +func (*GreetService) PointerStringInStringOut(in *string) *string { + return in +} + +func (*GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (*GreetService) StructInputStructOutput(in Person) Person { + return in +} + +func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + +func (*GreetService) MapIntInt(in map[int]int) { +} + +func (*GreetService) PointerMapIntInt(in *map[int]int) { +} + +func (*GreetService) MapIntIntPointer(in map[int]*int) { +} + +func (*GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (*GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + +func (*GreetService) ArrayInt(in [4]int) { +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single/bound_types.json b/v3/internal/generator/testcases/variable_single/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/variable_single/main.go b/v3/internal/generator/testcases/variable_single/main.go new file mode 100644 index 000000000..5baf1a04f --- /dev/null +++ b/v3/internal/generator/testcases/variable_single/main.go @@ -0,0 +1,37 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func main() { + greetService := application.NewService(&GreetService{}) + app := application.New(application.Options{ + Services: []application.Service{ + greetService, + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_function/bound_types.json b/v3/internal/generator/testcases/variable_single_from_function/bound_types.json new file mode 100644 index 000000000..a8b95e321 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_function/bound_types.json @@ -0,0 +1,3 @@ +[ + ".GreetService" +] diff --git a/v3/internal/generator/testcases/variable_single_from_function/main.go b/v3/internal/generator/testcases/variable_single_from_function/main.go new file mode 100644 index 000000000..247702051 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_function/main.go @@ -0,0 +1,42 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() application.Service { + return application.NewService(&GreetService{}) +} + +func main() { + greetService := NewGreetService() + app := application.New(application.Options{ + Services: []application.Service{ + greetService, + }, + }) + + _ = app.Window.New() // discard + // or: win := app.Window.New() // keep for later + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json b/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json new file mode 100644 index 000000000..86a0a8812 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/bound_types.json @@ -0,0 +1,4 @@ +[ + ".GreetService", + "/services.OtherService" +] diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/main.go b/v3/internal/generator/testcases/variable_single_from_other_function/main.go new file mode 100644 index 000000000..6bbb73f46 --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/main.go @@ -0,0 +1,53 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string + target *Person +} + +// Person is a person! +// They have a name and an address +type Person struct { + Name string + Address *services.Address +} + +// Greet does XYZ +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + +func main() { + otherService := services.NewOtherService() + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + otherService, + }, + }) + + app.Window.New() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go b/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go new file mode 100644 index 000000000..4ac6e6efd --- /dev/null +++ b/v3/internal/generator/testcases/variable_single_from_other_function/services/other.go @@ -0,0 +1,28 @@ +package services + +import "github.com/wailsapp/wails/v3/pkg/application" + +// OtherService is a struct +// that does things +type OtherService struct { + t int +} + +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } +} + +func NewOtherService() application.Service { + return application.NewService(&OtherService{}) +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js new file mode 100644 index 000000000..96abf0ef1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js new file mode 100644 index 000000000..59cfef5bd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..fcbd41a3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByID(1928502664, aliasValue).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByID(1896499664, p).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByID(2240931744).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByID(643456960).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByID(914093800).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByID(1411160069, $0, $1).then(/** @type {($result: any) => any} */(($result) => { + return $$createType7($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..4278c7958 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..3de57786d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,334 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + /** + * Creates a new AliasGroup instance. + * @param {Partial} [$$source = {}] - The source object to create the AliasGroup. + */ + constructor($$source = {}) { + if (!("GAi" in $$source)) { + /** + * @member + * @type {GenericAlias} + */ + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + /** + * @member + * @type {GenericAlias>} + */ + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + /** + * @member + * @type {GenericPtrAlias} + */ + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + /** + * @member + * @type {GenericPtrAlias>} + */ + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + /** + * @member + * @type {GenericMapAlias} + */ + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + /** + * @member + * @type {GenericPersonAlias} + */ + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + /** + * @member + * @type {IndirectPersonAlias} + */ + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + /** + * @member + * @type {TPIndirectPersonAlias} + */ + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + * @param {any} [$$source = {}] + * @returns {AliasGroup} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[]} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + */ +export class EmptyStruct { + /** + * Creates a new EmptyStruct instance. + * @param {Partial} [$$source = {}] - The source object to create the EmptyStruct. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + * @param {any} [$$source = {}] + * @returns {EmptyStruct} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U }} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + */ +export class GenericPerson { + /** + * Creates a new GenericPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the GenericPerson. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * @member + * @type {T | undefined} + */ + this["Name"] = undefined; + } + if (!("AliasedField" in $$source)) { + /** + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => GenericPerson} + */ + static createFrom($$createParamT) { + const $$createField0_0 = $$createParamT; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[]>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[]} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * The Person's name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + /** + * A random alias field. + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..42219054f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..5e27fbc9e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..fa634943d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..ab78e5ea3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded3} Embedded3 + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..7a0edf1c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,272 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Creates a new Embedded1 instance. + * @param {Partial} [$$source = {}] - The source object to create the Embedded1. + */ + constructor($$source = {}) { + if (!("Friends" in $$source)) { + /** + * Friends should be shadowed in Person by a field of lesser depth + * @member + * @type {number} + */ + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + * @member + * @type {number} + */ + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + * @member + * @type {string} + */ + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Embedded1} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * Titles is optional in JSON + * @member + * @type {Title[] | undefined} + */ + this["Titles"] = undefined; + } + if (!("Names" in $$source)) { + /** + * Names has a + * multiline comment + * @member + * @type {string[]} + */ + this["Names"] = []; + } + if (!("Partner" in $$source)) { + /** + * Partner has a custom and complex JSON key + * @member + * @type {Person | null} + */ + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + /** + * @member + * @type {(Person | null)[]} + */ + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + * @member + * @type {Embedded3 | null} + */ + this["StillThere"] = null; + } + if (!("-" in $$source)) { + /** + * StrangeNumber maps to "-" + * @member + * @type {number} + */ + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + /** + * Embedded3 should appear with key "Embedded3" + * @member + * @type {Embedded3} + */ + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + /** + * StrangerNumber is serialized as a string + * @member + * @type {`${number}`} + */ + this["StrangerNumber"] = "0"; + } + if (/** @type {any} */(false)) { + /** + * StrangestString is optional and serialized as a JSON string + * @member + * @type {`"${string}"` | undefined} + */ + this["StrangestString"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * StringStrangest is serialized as a JSON string and optional + * @member + * @type {`"${string}"` | undefined} + */ + this["StringStrangest"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * embedded4 should be optional and appear with key "emb4" + * @member + * @type {embedded4 | undefined} + */ + this["emb4"] = undefined; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +export class embedded4 { + /** + * Creates a new embedded4 instance. + * @param {Partial} [$$source = {}] - The source object to create the embedded4. + */ + constructor($$source = {}) { + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + * @member + * @type {boolean} + */ + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {embedded4} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..35fac10f2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[]} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null }} assoc + * @param {(number | null)[]} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[]]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..82af81baf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,38 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..29255dd9c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..0ad0efb4e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..f24f5a2c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,164 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + /** + * Creates a new StructA instance. + * @param {Partial} [$$source = {}] - The source object to create the StructA. + */ + constructor($$source = {}) { + if (!("B" in $$source)) { + /** + * @member + * @type {structB | null} + */ + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructA} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructC { + /** + * Creates a new StructC instance. + * @param {Partial} [$$source = {}] - The source object to create the StructC. + */ + constructor($$source = {}) { + if (!("D" in $$source)) { + /** + * @member + * @type {structD} + */ + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructC} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructE { + /** + * Creates a new StructE instance. + * @param {Partial} [$$source = {}] - The source object to create the StructE. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructE} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE(/** @type {Partial} */($$parsedSource)); + } +} + +export class structB { + /** + * Creates a new structB instance. + * @param {Partial} [$$source = {}] - The source object to create the structB. + */ + constructor($$source = {}) { + if (!("A" in $$source)) { + /** + * @member + * @type {StructA | null} + */ + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structB} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB(/** @type {Partial} */($$parsedSource)); + } +} + +export class structD { + /** + * Creates a new structD instance. + * @param {Partial} [$$source = {}] - The source object to create the structD. + */ + constructor($$source = {}) { + if (!("E" in $$source)) { + /** + * @member + * @type {StructE} + */ + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structD} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..faf090884 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,65 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + })); +} + +// Private type creation functions +var $$createType0 = /** @type {(...args: any[]) => any} */(function $$initCreateType0(...args) { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = /** @type {(...args: any[]) => any} */(function $$initCreateType4(...args) { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = /** @type {(...args: any[]) => any} */(function $$initCreateType9(...args) { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..47d41f572 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {{ [_: string]: Alias }[]} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[]}[]} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..972196ce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello JS!"); +console.log("Hello JS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..fce17fb1d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..291a3cecf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + /** + * Creates a new InternalModel instance. + * @param {Partial} [$$source = {}] - The source object to create the InternalModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {InternalModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + /** + * Creates a new unexportedModel instance. + * @param {Partial} [$$source = {}] - The source object to create the unexportedModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {unexportedModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..b7e83cfd4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..274f4eed4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + /** + * Creates a new Dummy instance. + * @param {Partial} [$$source = {}] - The source object to create the Dummy. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Dummy} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..cc7ed89d3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByID(3518775569, $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByID(474018228, $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..d3f53be87 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..a178744b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..ed402a8b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,41 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..d5d66d4cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..2c5df9ee7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Title" in $$source)) { + /** + * @member + * @type {Title} + */ + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Age" in $$source)) { + /** + * @member + * @type {Age} + */ + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..fbc2294e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..65ebfa2f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..0ab295133 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..1866aca09 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2007737399).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..29a95e11e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {other$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..293a2f0bb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2447353446).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..1bc7cb45b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..9fd9745c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..1dc5bfc38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,1957 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + */ +export class Maps { + /** + * Creates a new Maps instance. + * @param {Partial>} [$$source = {}] - The source object to create the Maps. + */ + constructor($$source = {}) { + if (!("Bool" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Rune"] = {}; + } + if (!("String" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: string]: number }} + */ + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerTextMarshaler]: number }} + */ + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["A"] = {}; + } + if (!("APtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointer]: number }} + */ + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WT"] = {}; + } + if (!("WA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WA"] = {}; + } + if (!("ST" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringType]: number }} + */ + this["ST"] = {}; + } + if (!("SA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringAlias]: number }} + */ + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerAlias]: number }} + */ + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ComparableCstrAlias]: number }} + */ + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: BasicCstrAlias]: number }} + */ + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: GoodTildeCstrAlias]: number }} + */ + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: NonBasicCstrAlias]: number }} + */ + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: InterfaceCstrAlias]: number }} + */ + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + * @template [R=any] + * @template [S=any] + * @template [T=any] + * @template [U=any] + * @template [V=any] + * @template [W=any] + * @template [X=any] + * @template [Y=any] + * @template [Z=any] + * @param {(source: any) => R} $$createParamR + * @param {(source: any) => S} $$createParamS + * @param {(source: any) => T} $$createParamT + * @param {(source: any) => U} $$createParamU + * @param {(source: any) => V} $$createParamV + * @param {(source: any) => W} $$createParamW + * @param {(source: any) => X} $$createParamX + * @param {(source: any) => Y} $$createParamY + * @param {(source: any) => Z} $$createParamZ + * @returns {($$source?: any) => Maps} + */ + static createFrom($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType60 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType61 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType62 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType63 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType64 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType65 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType66 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType67 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType68 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType69 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType70 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType71 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType72 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType73 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType74 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType75 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType76 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..870b2804b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByID(4021345184).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..8f525252e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,116 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..77fe552ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,630 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +export class Data { + /** + * Creates a new Data instance. + * @param {Partial} [$$source = {}] - The source object to create the Data. + */ + constructor($$source = {}) { + if (!("NM" in $$source)) { + /** + * @member + * @type {NonMarshaler} + */ + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + /** + * NonMarshaler | null + * @member + * @type {NonMarshaler | null} + */ + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + /** + * @member + * @type {ValueJsonMarshaler} + */ + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + /** + * ValueJsonMarshaler | null + * @member + * @type {ValueJsonMarshaler | null} + */ + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + /** + * @member + * @type {PointerJsonMarshaler} + */ + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + /** + * PointerJsonMarshaler | null + * @member + * @type {PointerJsonMarshaler | null} + */ + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + /** + * @member + * @type {ValueTextMarshaler} + */ + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + /** + * ValueTextMarshaler | null + * @member + * @type {ValueTextMarshaler | null} + */ + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + /** + * @member + * @type {PointerTextMarshaler} + */ + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + /** + * PointerTextMarshaler | null + * @member + * @type {PointerTextMarshaler | null} + */ + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + /** + * @member + * @type {ValueMarshaler} + */ + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + /** + * ValueMarshaler | null + * @member + * @type {ValueMarshaler | null} + */ + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + /** + * @member + * @type {PointerMarshaler} + */ + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + /** + * PointerMarshaler | null + * @member + * @type {PointerMarshaler | null} + */ + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + /** + * @member + * @type {UnderlyingJsonMarshaler} + */ + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + /** + * UnderlyingJsonMarshaler | null + * @member + * @type {UnderlyingJsonMarshaler | null} + */ + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + /** + * @member + * @type {UnderlyingTextMarshaler} + */ + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + /** + * UnderlyingTextMarshaler | null + * @member + * @type {UnderlyingTextMarshaler | null} + */ + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + /** + * @member + * @type {UnderlyingMarshaler} + */ + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + /** + * UnderlyingMarshaler | null + * @member + * @type {UnderlyingMarshaler | null} + */ + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + /** + * @member + * @type {AliasNonMarshaler} + */ + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + /** + * AliasNonMarshaler | null + * @member + * @type {AliasNonMarshaler | null} + */ + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + /** + * @member + * @type {AliasJsonMarshaler} + */ + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + /** + * AliasJsonMarshaler | null + * @member + * @type {AliasJsonMarshaler | null} + */ + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + /** + * @member + * @type {AliasTextMarshaler} + */ + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + /** + * AliasTextMarshaler | null + * @member + * @type {AliasTextMarshaler | null} + */ + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + /** + * @member + * @type {AliasMarshaler} + */ + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + /** + * AliasMarshaler | null + * @member + * @type {AliasMarshaler | null} + */ + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + /** + * @member + * @type {ImplicitJsonMarshaler} + */ + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + /** + * ImplicitJsonMarshaler | null + * @member + * @type {ImplicitJsonMarshaler | null} + */ + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + /** + * @member + * @type {ImplicitTextMarshaler} + */ + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + /** + * ImplicitTextMarshaler | null + * @member + * @type {ImplicitTextMarshaler | null} + */ + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + /** + * @member + * @type {ImplicitMarshaler} + */ + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + /** + * ImplicitMarshaler | null + * @member + * @type {ImplicitMarshaler | null} + */ + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + /** + * @member + * @type {ImplicitNonJson} + */ + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + /** + * ImplicitNonJson | null + * @member + * @type {ImplicitNonJson | null} + */ + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + /** + * @member + * @type {ImplicitNonText} + */ + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + /** + * ImplicitNonText | null + * @member + * @type {ImplicitNonText | null} + */ + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + /** + * @member + * @type {ImplicitNonMarshaler} + */ + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + /** + * ImplicitNonMarshaler | null + * @member + * @type {ImplicitNonMarshaler | null} + */ + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + /** + * @member + * @type {ImplicitJsonButText} + */ + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + /** + * ImplicitJsonButText | null + * @member + * @type {ImplicitJsonButText | null} + */ + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + /** + * @member + * @type {ImplicitTextButJson} + */ + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + /** + * ImplicitTextButJson | null + * @member + * @type {ImplicitTextButJson | null} + */ + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Data} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + /** + * Creates a new ImplicitNonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the ImplicitNonMarshaler. + */ + constructor($$source = {}) { + if (!("Marshaler" in $$source)) { + /** + * @member + * @type {json$0.Marshaler} + */ + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + /** + * @member + * @type {encoding$0.TextMarshaler} + */ + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {ImplicitNonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + */ +export class NonMarshaler { + /** + * Creates a new NonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the NonMarshaler. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {NonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..5b415f83e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByID(4021345184).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..9be766c03 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..b42e223fa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,181 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + */ +export class HowDifferent { + /** + * Creates a new HowDifferent instance. + * @param {Partial>} [$$source = {}] - The source object to create the HowDifferent. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {{ [_: string]: How }[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + * @template [How=any] + * @param {(source: any) => How} $$createParamHow + * @returns {($$source?: any) => HowDifferent} + */ + static createFrom($$createParamHow) { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +export class personImpl { + /** + * Creates a new personImpl instance. + * @param {Partial} [$$source = {}] - The source object to create the personImpl. + */ + constructor($$source = {}) { + if (!("Nickname" in $$source)) { + /** + * Nickname conceals a person's identity. + * @member + * @type {string} + */ + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + * @param {any} [$$source = {}] + * @returns {personImpl} + */ + static createFrom($$source = {}) { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Map($Create.Any, $$createParamHow)); +const $$createType1 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Array($$createType0($$createParamHow))); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..db4e64147 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..89992cacf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,60 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + */ +export class OtherPerson { + /** + * Creates a new OtherPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the OtherPerson. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {T[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => OtherPerson} + */ + static createFrom($$createParamT) { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamT) => $Create.Array($$createParamT)); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..36b25c183 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..a87ccf6c4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2124352079).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(4281222271); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..5fb44fbc6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..5ec6c820e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2590614085).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(773650321); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..8afbd8b3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..f017774d8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e50a4a6ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..f017774d8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..04771d2ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..24dc0334e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(3568225479).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..855602e98 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..855602e98 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..9dfe48511 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..50737a34b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..d7bfe75cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,58 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..de3c9d51d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(1491748400).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js new file mode 100644 index 000000000..96abf0ef1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js new file mode 100644 index 000000000..59cfef5bd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..2352f40bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByName("main.GreetService.Get", aliasValue).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByName("main.GreetService.GetButAliased", p).then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByName("main.GreetService.GetButDifferent").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias").then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByName("main.GreetService.GetButGenericAliases").then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByName("main.GreetService.Greet", $0, $1).then(/** @type {($result: any) => any} */(($result) => { + return $$createType7($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..4278c7958 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..3de57786d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,334 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + /** + * Creates a new AliasGroup instance. + * @param {Partial} [$$source = {}] - The source object to create the AliasGroup. + */ + constructor($$source = {}) { + if (!("GAi" in $$source)) { + /** + * @member + * @type {GenericAlias} + */ + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + /** + * @member + * @type {GenericAlias>} + */ + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + /** + * @member + * @type {GenericPtrAlias} + */ + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + /** + * @member + * @type {GenericPtrAlias>} + */ + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + /** + * @member + * @type {GenericMapAlias} + */ + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + /** + * @member + * @type {GenericPersonAlias} + */ + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + /** + * @member + * @type {IndirectPersonAlias} + */ + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + /** + * @member + * @type {TPIndirectPersonAlias} + */ + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + * @param {any} [$$source = {}] + * @returns {AliasGroup} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[]} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + */ +export class EmptyStruct { + /** + * Creates a new EmptyStruct instance. + * @param {Partial} [$$source = {}] - The source object to create the EmptyStruct. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + * @param {any} [$$source = {}] + * @returns {EmptyStruct} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U }} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + */ +export class GenericPerson { + /** + * Creates a new GenericPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the GenericPerson. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * @member + * @type {T | undefined} + */ + this["Name"] = undefined; + } + if (!("AliasedField" in $$source)) { + /** + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => GenericPerson} + */ + static createFrom($$createParamT) { + const $$createField0_0 = $$createParamT; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[]>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[]} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * The Person's name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + /** + * A random alias field. + * @member + * @type {Alias} + */ + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..54e794649 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..f6e8901a9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..b5c320f3d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..ab78e5ea3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded3} Embedded3 + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..7a0edf1c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,272 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Creates a new Embedded1 instance. + * @param {Partial} [$$source = {}] - The source object to create the Embedded1. + */ + constructor($$source = {}) { + if (!("Friends" in $$source)) { + /** + * Friends should be shadowed in Person by a field of lesser depth + * @member + * @type {number} + */ + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + * @member + * @type {number} + */ + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + * @member + * @type {string} + */ + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Embedded1} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (/** @type {any} */(false)) { + /** + * Titles is optional in JSON + * @member + * @type {Title[] | undefined} + */ + this["Titles"] = undefined; + } + if (!("Names" in $$source)) { + /** + * Names has a + * multiline comment + * @member + * @type {string[]} + */ + this["Names"] = []; + } + if (!("Partner" in $$source)) { + /** + * Partner has a custom and complex JSON key + * @member + * @type {Person | null} + */ + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + /** + * @member + * @type {(Person | null)[]} + */ + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + * @member + * @type {Embedded3 | null} + */ + this["StillThere"] = null; + } + if (!("-" in $$source)) { + /** + * StrangeNumber maps to "-" + * @member + * @type {number} + */ + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + /** + * Embedded3 should appear with key "Embedded3" + * @member + * @type {Embedded3} + */ + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + /** + * StrangerNumber is serialized as a string + * @member + * @type {`${number}`} + */ + this["StrangerNumber"] = "0"; + } + if (/** @type {any} */(false)) { + /** + * StrangestString is optional and serialized as a JSON string + * @member + * @type {`"${string}"` | undefined} + */ + this["StrangestString"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * StringStrangest is serialized as a JSON string and optional + * @member + * @type {`"${string}"` | undefined} + */ + this["StringStrangest"] = undefined; + } + if (/** @type {any} */(false)) { + /** + * embedded4 should be optional and appear with key "emb4" + * @member + * @type {embedded4 | undefined} + */ + this["emb4"] = undefined; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +export class embedded4 { + /** + * Creates a new embedded4 instance. + * @param {Partial} [$$source = {}] - The source object to create the embedded4. + */ + constructor($$source = {}) { + if (!("NamingThingsIsHard" in $$source)) { + /** + * NamingThingsIsHard is a law of programming + * @member + * @type {`${boolean}`} + */ + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + * @member + * @type {boolean} + */ + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + * @param {any} [$$source = {}] + * @returns {embedded4} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..fae9496c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[]} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null }} assoc + * @param {(number | null)[]} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[]]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other).then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..82af81baf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,38 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..651fd2d97 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + })); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..0ad0efb4e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..f24f5a2c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,164 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + /** + * Creates a new StructA instance. + * @param {Partial} [$$source = {}] - The source object to create the StructA. + */ + constructor($$source = {}) { + if (!("B" in $$source)) { + /** + * @member + * @type {structB | null} + */ + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructA} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructC { + /** + * Creates a new StructC instance. + * @param {Partial} [$$source = {}] - The source object to create the StructC. + */ + constructor($$source = {}) { + if (!("D" in $$source)) { + /** + * @member + * @type {structD} + */ + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructC} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC(/** @type {Partial} */($$parsedSource)); + } +} + +export class StructE { + /** + * Creates a new StructE instance. + * @param {Partial} [$$source = {}] - The source object to create the StructE. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + * @param {any} [$$source = {}] + * @returns {StructE} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE(/** @type {Partial} */($$parsedSource)); + } +} + +export class structB { + /** + * Creates a new structB instance. + * @param {Partial} [$$source = {}] - The source object to create the structB. + */ + constructor($$source = {}) { + if (!("A" in $$source)) { + /** + * @member + * @type {StructA | null} + */ + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structB} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB(/** @type {Partial} */($$parsedSource)); + } +} + +export class structD { + /** + * Creates a new structD instance. + * @param {Partial} [$$source = {}] - The source object to create the structD. + */ + constructor($$source = {}) { + if (!("E" in $$source)) { + /** + * @member + * @type {StructE} + */ + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + * @param {any} [$$source = {}] + * @returns {structD} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..b4cbaa216 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,65 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + })); +} + +// Private type creation functions +var $$createType0 = /** @type {(...args: any[]) => any} */(function $$initCreateType0(...args) { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = /** @type {(...args: any[]) => any} */(function $$initCreateType4(...args) { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = /** @type {(...args: any[]) => any} */(function $$initCreateType9(...args) { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..47d41f572 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {{ [_: string]: Alias }[]} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[]}[]} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..972196ce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello JS!"); +console.log("Hello JS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..51a9c6be9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..291a3cecf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,69 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + /** + * Creates a new InternalModel instance. + * @param {Partial} [$$source = {}] - The source object to create the InternalModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {InternalModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + /** + * Creates a new unexportedModel instance. + * @param {Partial} [$$source = {}] - The source object to create the unexportedModel. + */ + constructor($$source = {}) { + if (!("Field" in $$source)) { + /** + * @member + * @type {string} + */ + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + * @param {any} [$$source = {}] + * @returns {unexportedModel} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..b7e83cfd4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..274f4eed4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,28 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + /** + * Creates a new Dummy instance. + * @param {Partial} [$$source = {}] - The source object to create the Dummy. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Dummy} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy(/** @type {Partial} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js new file mode 100644 index 000000000..2166d33b6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js new file mode 100644 index 000000000..338898726 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..c59f8e184 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js new file mode 100644 index 000000000..ddf4920e5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_jc.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..8a93f316f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..494240982 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..fd18bf8a3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,41 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..d5d66d4cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..2c5df9ee7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Title" in $$source)) { + /** + * @member + * @type {Title} + */ + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Age" in $$source)) { + /** + * @member + * @type {Age} + */ + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..125c76d13 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..65ebfa2f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..0ab295133 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..ba2c614cd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..29a95e11e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {other$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..f6269f0b5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..e91d18592 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..9fd9745c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,97 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..1dc5bfc38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,1957 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + */ +export class Maps { + /** + * Creates a new Maps instance. + * @param {Partial>} [$$source = {}] - The source object to create the Maps. + */ + constructor($$source = {}) { + if (!("Bool" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["Rune"] = {}; + } + if (!("String" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: string]: number }} + */ + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueTextMarshaler]: number }} + */ + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerTextMarshaler]: number }} + */ + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["A"] = {}; + } + if (!("APtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValue]: number }} + */ + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedValuePtr]: number }} + */ + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointer]: number }} + */ + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedPointerPtr]: number }} + */ + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedCustomInterface]: number }} + */ + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: EmbeddedOriginalInterface]: number }} + */ + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WT"] = {}; + } + if (!("WA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["WA"] = {}; + } + if (!("ST" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringType]: number }} + */ + this["ST"] = {}; + } + if (!("SA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: StringAlias]: number }} + */ + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ValueAlias]: number }} + */ + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointerAlias]: number }} + */ + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + /** + * Soft reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: ComparableCstrAlias]: number }} + */ + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: BasicCstrAlias]: number }} + */ + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: GoodTildeCstrAlias]: number }} + */ + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: NonBasicCstrAlias]: number }} + */ + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: PointableCstrAlias]: number }} + */ + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: `${number}`]: number }} + */ + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: MixedCstrAlias]: number }} + */ + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + /** + * Accept + * @member + * @type {{ [_: InterfaceCstrAlias]: number }} + */ + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + /** + * Accept, hide + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + /** + * Reject + * @member + * @type {{ [_: string]: number }} + */ + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + * @template [R=any] + * @template [S=any] + * @template [T=any] + * @template [U=any] + * @template [V=any] + * @template [W=any] + * @template [X=any] + * @template [Y=any] + * @template [Z=any] + * @param {(source: any) => R} $$createParamR + * @param {(source: any) => S} $$createParamS + * @param {(source: any) => T} $$createParamT + * @param {(source: any) => U} $$createParamU + * @param {(source: any) => V} $$createParamV + * @param {(source: any) => W} $$createParamW + * @param {(source: any) => X} $$createParamX + * @param {(source: any) => Y} $$createParamY + * @param {(source: any) => Z} $$createParamZ + * @returns {($$source?: any) => Maps} + */ + static createFrom($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType60 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType61 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType62 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType63 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType64 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType65 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType66 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType67 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType68 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType69 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType70 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType71 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType72 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType73 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType74 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType75 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType76 = /** @type {(...args: any[]) => any} */(($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ) => $Create.Map($Create.Any, $Create.Any)); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..cc98f3a89 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByName("main.Service.Method").then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..8f525252e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,116 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..77fe552ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,630 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +export class Data { + /** + * Creates a new Data instance. + * @param {Partial} [$$source = {}] - The source object to create the Data. + */ + constructor($$source = {}) { + if (!("NM" in $$source)) { + /** + * @member + * @type {NonMarshaler} + */ + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + /** + * NonMarshaler | null + * @member + * @type {NonMarshaler | null} + */ + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + /** + * @member + * @type {ValueJsonMarshaler} + */ + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + /** + * ValueJsonMarshaler | null + * @member + * @type {ValueJsonMarshaler | null} + */ + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + /** + * @member + * @type {PointerJsonMarshaler} + */ + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + /** + * PointerJsonMarshaler | null + * @member + * @type {PointerJsonMarshaler | null} + */ + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + /** + * @member + * @type {ValueTextMarshaler} + */ + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + /** + * ValueTextMarshaler | null + * @member + * @type {ValueTextMarshaler | null} + */ + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + /** + * @member + * @type {PointerTextMarshaler} + */ + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + /** + * PointerTextMarshaler | null + * @member + * @type {PointerTextMarshaler | null} + */ + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + /** + * @member + * @type {ValueMarshaler} + */ + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + /** + * ValueMarshaler | null + * @member + * @type {ValueMarshaler | null} + */ + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + /** + * @member + * @type {PointerMarshaler} + */ + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + /** + * PointerMarshaler | null + * @member + * @type {PointerMarshaler | null} + */ + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + /** + * @member + * @type {UnderlyingJsonMarshaler} + */ + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + /** + * UnderlyingJsonMarshaler | null + * @member + * @type {UnderlyingJsonMarshaler | null} + */ + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + /** + * @member + * @type {UnderlyingTextMarshaler} + */ + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + /** + * UnderlyingTextMarshaler | null + * @member + * @type {UnderlyingTextMarshaler | null} + */ + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + /** + * @member + * @type {UnderlyingMarshaler} + */ + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + /** + * UnderlyingMarshaler | null + * @member + * @type {UnderlyingMarshaler | null} + */ + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + /** + * string + * @member + * @type {string} + */ + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + /** + * string | null + * @member + * @type {string | null} + */ + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + /** + * any + * @member + * @type {any} + */ + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + /** + * any | null + * @member + * @type {any | null} + */ + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + /** + * @member + * @type {AliasNonMarshaler} + */ + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + /** + * AliasNonMarshaler | null + * @member + * @type {AliasNonMarshaler | null} + */ + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + /** + * @member + * @type {AliasJsonMarshaler} + */ + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + /** + * AliasJsonMarshaler | null + * @member + * @type {AliasJsonMarshaler | null} + */ + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + /** + * @member + * @type {AliasTextMarshaler} + */ + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + /** + * AliasTextMarshaler | null + * @member + * @type {AliasTextMarshaler | null} + */ + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + /** + * @member + * @type {AliasMarshaler} + */ + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + /** + * AliasMarshaler | null + * @member + * @type {AliasMarshaler | null} + */ + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + /** + * @member + * @type {ImplicitJsonMarshaler} + */ + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + /** + * ImplicitJsonMarshaler | null + * @member + * @type {ImplicitJsonMarshaler | null} + */ + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + /** + * @member + * @type {ImplicitTextMarshaler} + */ + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + /** + * ImplicitTextMarshaler | null + * @member + * @type {ImplicitTextMarshaler | null} + */ + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + /** + * @member + * @type {ImplicitMarshaler} + */ + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + /** + * ImplicitMarshaler | null + * @member + * @type {ImplicitMarshaler | null} + */ + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + /** + * @member + * @type {ImplicitNonJson} + */ + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + /** + * ImplicitNonJson | null + * @member + * @type {ImplicitNonJson | null} + */ + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + /** + * @member + * @type {ImplicitNonText} + */ + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + /** + * ImplicitNonText | null + * @member + * @type {ImplicitNonText | null} + */ + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + /** + * @member + * @type {ImplicitNonMarshaler} + */ + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + /** + * ImplicitNonMarshaler | null + * @member + * @type {ImplicitNonMarshaler | null} + */ + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + /** + * @member + * @type {ImplicitJsonButText} + */ + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + /** + * ImplicitJsonButText | null + * @member + * @type {ImplicitJsonButText | null} + */ + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + /** + * @member + * @type {ImplicitTextButJson} + */ + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + /** + * ImplicitTextButJson | null + * @member + * @type {ImplicitTextButJson | null} + */ + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Data} + */ + static createFrom($$source = {}) { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + /** + * Creates a new ImplicitNonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the ImplicitNonMarshaler. + */ + constructor($$source = {}) { + if (!("Marshaler" in $$source)) { + /** + * @member + * @type {json$0.Marshaler} + */ + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + /** + * @member + * @type {encoding$0.TextMarshaler} + */ + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {ImplicitNonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + */ +export class NonMarshaler { + /** + * Creates a new NonMarshaler instance. + * @param {Partial} [$$source = {}] - The source object to create the NonMarshaler. + */ + constructor($$source = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + * @param {any} [$$source = {}] + * @returns {NonMarshaler} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..e6c23e4f0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByName("main.Service.Method").then(/** @type {($result: any) => any} */(($result) => { + return $$createType0($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..9be766c03 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..b42e223fa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,181 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + */ +export class HowDifferent { + /** + * Creates a new HowDifferent instance. + * @param {Partial>} [$$source = {}] - The source object to create the HowDifferent. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {{ [_: string]: How }[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + * @template [How=any] + * @param {(source: any) => How} $$createParamHow + * @returns {($$source?: any) => HowDifferent} + */ + static createFrom($$createParamHow) { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +export class personImpl { + /** + * Creates a new personImpl instance. + * @param {Partial} [$$source = {}] - The source object to create the personImpl. + */ + constructor($$source = {}) { + if (!("Nickname" in $$source)) { + /** + * Nickname conceals a person's identity. + * @member + * @type {string} + */ + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + /** + * They have a name. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + /** + * Exactly 4 sketchy friends. + * @member + * @type {Impersonator[]} + */ + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + * @param {any} [$$source = {}] + * @returns {personImpl} + */ + static createFrom($$source = {}) { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl(/** @type {Partial} */($$parsedSource)); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Map($Create.Any, $$createParamHow)); +const $$createType1 = /** @type {(...args: any[]) => any} */(($$createParamHow) => $Create.Array($$createType0($$createParamHow))); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..db4e64147 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..89992cacf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,60 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + */ +export class OtherPerson { + /** + * Creates a new OtherPerson instance. + * @param {Partial>} [$$source = {}] - The source object to create the OtherPerson. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * They have a name as well. + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + /** + * But they may have many differences. + * @member + * @type {T[]} + */ + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + * @template [T=any] + * @param {(source: any) => T} $$createParamT + * @returns {($$source?: any) => OtherPerson} + */ + static createFrom($$createParamT) { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson(/** @type {Partial>} */($$parsedSource)); + }; + } +} + +// Private type creation functions +const $$createType0 = /** @type {(...args: any[]) => any} */(($$createParamT) => $Create.Array($$createParamT)); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..d2819bc4b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..35bc3579d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..0497b16bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..903e4153f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,42 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("main.EmbedService.LikeThisOne").then(/** @type {($result: any) => any} */(($result) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + })); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..fe683f548 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..69652f626 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e6a0e3a74 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..69652f626 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..04771d2ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,54 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..327b15c9c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..d811d97ce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..d811d97ce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,379 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number }} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null }} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] }} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] }>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType2($result); + })); +} + +/** + * @param {string[]} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType3($result); + })); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(/** @type {($result: any) => any} */(($result) => { + return $$createType4($result); + })); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..69c96370a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,57 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + /** + * @member + * @type {Person | null} + */ + this["Parent"] = null; + } + if (!("Details" in $$source)) { + /** + * @member + * @type {{"Age": number, "Address": {"Street": string}}} + */ + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..6364fa8f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..16a94df37 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,40 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name).then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..fefe6b6ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..d7bfe75cf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,58 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + /** + * Creates a new Person instance. + * @param {Partial} [$$source = {}] - The source object to create the Person. + */ + constructor($$source = {}) { + if (!("Name" in $$source)) { + /** + * @member + * @type {string} + */ + this["Name"] = ""; + } + if (!("Address" in $$source)) { + /** + * @member + * @type {services$0.Address | null} + */ + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Person} + */ + static createFrom($$source = {}) { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person(/** @type {Partial} */($$parsedSource)); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..ed65b6d15 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..24a5de807 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,49 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + /** + * Creates a new Address instance. + * @param {Partial
} [$$source = {}] - The source object to create the Address. + */ + constructor($$source = {}) { + if (!("Street" in $$source)) { + /** + * @member + * @type {string} + */ + this["Street"] = ""; + } + if (!("State" in $$source)) { + /** + * @member + * @type {string} + */ + this["State"] = ""; + } + if (!("Country" in $$source)) { + /** + * @member + * @type {string} + */ + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + * @param {any} [$$source = {}] + * @returns {Address} + */ + static createFrom($$source = {}) { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address(/** @type {Partial
} */($$parsedSource)); + } +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..d413366d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,31 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay").then(/** @type {($result: any) => any} */(($result) => { + return $$createType1($result); + })); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js new file mode 100644 index 000000000..5dce4eea6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js new file mode 100644 index 000000000..db89bafbc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..16b76f94c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,70 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByID(1928502664, aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByID(1896499664, p); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByID(2240931744); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByID(643456960); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByID(914093800); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByID(1411160069, $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..5a5c62644 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,96 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {$models.AliasGroup} AliasGroup + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * A class alias. + * @typedef {$models.AliasedPerson} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef {$models.EmptyStruct} EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {$models.GenericPerson} GenericPerson + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {$models.GenericPersonAlias} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {$models.IndirectPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ + +/** + * A non-generic struct containing an alias. + * @typedef {$models.Person} Person + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {$models.StrangelyAliasedPerson} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {$models.TPIndirectPersonAlias} TPIndirectPersonAlias + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..1ba2af395 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,112 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {Object} AliasGroup + * @property {GenericAlias} GAi + * @property {GenericAlias>} GAP + * @property {GenericPtrAlias} GPAs + * @property {GenericPtrAlias>} GPAP + * @property {GenericMapAlias} GMA + * @property {GenericPersonAlias} GPA + * @property {IndirectPersonAlias} IPA + * @property {TPIndirectPersonAlias} TPIPA + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[] | null} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef { { + * } } EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U } | null} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {Object} GenericPerson + * @property {T} Name + * @property {Alias} AliasedField + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[] | null>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[] | null} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + * @typedef {Object} Person + * @property {string} Name - The Person's name. + * @property {Alias} AliasedField - A random alias field. + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..2a4188bce --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..ead6f5da4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..995d46c13 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..f21130b86 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded1} Embedded1 + */ + +/** + * @typedef {$models.Embedded3} Embedded3 + */ + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..b30630777 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Embedded1 + * @property {number} Friends - Friends should be shadowed in Person by a field of lesser depth + * @property {number} Vanish - Vanish should be omitted from Person because there is another field with same depth and no tag + * @property {string} StillThere - StillThere should be shadowed in Person by other field with same depth and a json tag + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + */ + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + * @typedef { { + * "Titles"?: Title[] | null, + * "Names": string[] | null, + * "Partner": Person | null, + * "Friends": (Person | null)[] | null, + * "NamingThingsIsHard": `${boolean}`, + * "StillThere": Embedded3 | null, + * "-": number, + * "Embedded3": Embedded3, + * "StrangerNumber": `${number}`, + * "StrangestString"?: `"${string}"`, + * "StringStrangest"?: `"${string}"`, + * "emb4"?: embedded4, + * } } Person + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +/** + * @typedef {Object} embedded4 + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + * @property {boolean} Friends - Friends should not be shadowed in Person as embedded4 is not embedded from encoding/json's point of view; however, it should be shadowed in Embedded1 + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..0285f6ca4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[] | null} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null } | null} assoc + * @param {(number | null)[] | null} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[] | null]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..88af1203b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..035bc0792 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + * @typedef {Object} Person + * @property {string} Name + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..53027124b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..9b49a9172 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.StructA} StructA + */ + +/** + * @typedef {$models.StructC} StructC + */ + +/** + * @typedef {$models.StructE} StructE + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..50e0d0fc0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} StructA + * @property {structB | null} B + */ + +/** + * @typedef {Object} StructC + * @property {structD} D + */ + +/** + * @typedef { { + * } } StructE + */ + +/** + * @typedef {Object} structB + * @property {StructA | null} A + */ + +/** + * @typedef {Object} structD + * @property {StructE} E + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..382e6bac3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..2413995ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {({ [_: string]: Alias } | null)[] | null} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[] | null}[] | null} GenericCyclic + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..40d68bf85 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello JS!"); +console.log("Hello JS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..f96a46e77 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..a4c233c8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + * @typedef {Object} InternalModel + * @property {string} Field + */ + +/** + * An unexported model. + * @typedef {Object} unexportedModel + * @property {string} Field + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..c93da8f05 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,9 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Dummy} Dummy + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..6b6f5401f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef { { + * } } Dummy + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..0d869be84 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByID(3518775569, $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByID(474018228, $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..573852d5a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..d364009d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..f14b3b6b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..649d8d016 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..e7c70729c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + * @typedef {Object} Person + * @property {Title} Title + * @property {string} Name + * @property {Age} Age + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..508c12b72 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..e0e2d3014 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..26922b7eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..5743a9055 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..dff4e0d5f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2007737399); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..8a9890617 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {other$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..182a9e091 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(2447353446); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..be0cf5ce8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..80fdcd24c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {$models.Maps} Maps + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..cee61a5e4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,240 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {Object} Maps + * @property {{ [_: string]: number } | null} Bool - Reject + * @property {{ [_: `${number}`]: number } | null} Int - Accept + * @property {{ [_: `${number}`]: number } | null} Uint - Accept + * @property {{ [_: string]: number } | null} Float - Reject + * @property {{ [_: string]: number } | null} Complex - Reject + * @property {{ [_: `${number}`]: number } | null} Byte - Accept + * @property {{ [_: `${number}`]: number } | null} Rune - Accept + * @property {{ [_: string]: number } | null} String - Accept + * @property {{ [_: string]: number } | null} IntPtr - Reject + * @property {{ [_: string]: number } | null} UintPtr - Reject + * @property {{ [_: string]: number } | null} FloatPtr - Reject + * @property {{ [_: string]: number } | null} ComplexPtr - Reject + * @property {{ [_: string]: number } | null} StringPtr - Reject + * @property {{ [_: string]: number } | null} NTM - Reject + * @property {{ [_: string]: number } | null} NTMPtr - Reject + * @property {{ [_: ValueTextMarshaler]: number } | null} VTM - Accept + * @property {{ [_: ValueTextMarshaler]: number } | null} VTMPtr - Accept + * @property {{ [_: string]: number } | null} PTM - Reject + * @property {{ [_: PointerTextMarshaler]: number } | null} PTMPtr - Accept + * @property {{ [_: string]: number } | null} JTM - Accept, hide + * @property {{ [_: string]: number } | null} JTMPtr - Accept, hide + * @property {{ [_: string]: number } | null} A - Reject + * @property {{ [_: string]: number } | null} APtr - Reject + * @property {{ [_: string]: number } | null} TM - Accept, hide + * @property {{ [_: string]: number } | null} TMPtr - Reject + * @property {{ [_: string]: number } | null} CI - Accept, hide + * @property {{ [_: string]: number } | null} CIPtr - Reject + * @property {{ [_: string]: number } | null} EI - Accept, hide + * @property {{ [_: string]: number } | null} EIPtr - Reject + * @property {{ [_: EmbeddedValue]: number } | null} EV - Accept + * @property {{ [_: EmbeddedValue]: number } | null} EVPtr - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVP - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVPPtr - Accept + * @property {{ [_: string]: number } | null} EP - Reject + * @property {{ [_: EmbeddedPointer]: number } | null} EPPtr - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPP - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPPPtr - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECI - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECIPtr - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOI - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOIPtr - Accept + * @property {{ [_: string]: number } | null} WT - Reject + * @property {{ [_: string]: number } | null} WA - Reject + * @property {{ [_: StringType]: number } | null} ST - Accept + * @property {{ [_: StringAlias]: number } | null} SA - Accept + * @property {{ [_: `${number}`]: number } | null} IntT - Accept + * @property {{ [_: `${number}`]: number } | null} IntA - Accept + * @property {{ [_: string]: number } | null} VT - Reject + * @property {{ [_: string]: number } | null} VTPtr - Reject + * @property {{ [_: string]: number } | null} VPT - Reject + * @property {{ [_: string]: number } | null} VPTPtr - Reject + * @property {{ [_: ValueAlias]: number } | null} VA - Accept + * @property {{ [_: ValueAlias]: number } | null} VAPtr - Accept + * @property {{ [_: string]: number } | null} VPA - Accept, hide + * @property {{ [_: string]: number } | null} VPAPtr - Reject + * @property {{ [_: string]: number } | null} PT - Reject + * @property {{ [_: string]: number } | null} PTPtr - Reject + * @property {{ [_: string]: number } | null} PPT - Reject + * @property {{ [_: string]: number } | null} PPTPtr - Reject + * @property {{ [_: string]: number } | null} PA - Reject + * @property {{ [_: PointerAlias]: number } | null} PAPtr - Accept + * @property {{ [_: string]: number } | null} PPA - Accept, hide + * @property {{ [_: string]: number } | null} PPAPtr - Reject + * @property {{ [_: string]: number } | null} IT - Accept, hide + * @property {{ [_: string]: number } | null} ITPtr - Reject + * @property {{ [_: string]: number } | null} IPT - Reject + * @property {{ [_: string]: number } | null} IPTPtr - Reject + * @property {{ [_: string]: number } | null} IA - Accept, hide + * @property {{ [_: string]: number } | null} IAPtr - Reject + * @property {{ [_: string]: number } | null} IPA - Reject + * @property {{ [_: string]: number } | null} IPAPtr - Reject + * @property {{ [_: string]: number } | null} TPR - Soft reject + * @property {{ [_: string]: number } | null} TPRPtr - Soft reject + * @property {{ [_: string]: number } | null} TPS - Accept, hide + * @property {{ [_: string]: number } | null} TPSPtr - Soft reject + * @property {{ [_: string]: number } | null} TPT - Soft reject + * @property {{ [_: string]: number } | null} TPTPtr - Soft reject + * @property {{ [_: string]: number } | null} TPU - Accept, hide + * @property {{ [_: string]: number } | null} TPUPtr - Soft reject + * @property {{ [_: string]: number } | null} TPV - Accept, hide + * @property {{ [_: string]: number } | null} TPVPtr - Soft reject + * @property {{ [_: string]: number } | null} TPW - Soft reject + * @property {{ [_: string]: number } | null} TPWPtr - Accept, hide + * @property {{ [_: string]: number } | null} TPX - Accept, hide + * @property {{ [_: string]: number } | null} TPXPtr - Soft reject + * @property {{ [_: string]: number } | null} TPY - Accept, hide + * @property {{ [_: string]: number } | null} TPYPtr - Soft reject + * @property {{ [_: string]: number } | null} TPZ - Accept, hide + * @property {{ [_: string]: number } | null} TPZPtr - Soft reject + * @property {{ [_: string]: number } | null} GAR - Soft reject + * @property {{ [_: string]: number } | null} GARPtr - Soft reject + * @property {{ [_: string]: number } | null} GAS - Accept, hide + * @property {{ [_: string]: number } | null} GASPtr - Soft reject + * @property {{ [_: string]: number } | null} GAT - Soft reject + * @property {{ [_: string]: number } | null} GATPtr - Soft reject + * @property {{ [_: string]: number } | null} GAU - Accept, hide + * @property {{ [_: string]: number } | null} GAUPtr - Soft reject + * @property {{ [_: string]: number } | null} GAV - Accept, hide + * @property {{ [_: string]: number } | null} GAVPtr - Soft reject + * @property {{ [_: string]: number } | null} GAW - Soft reject + * @property {{ [_: string]: number } | null} GAWPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAX - Accept, hide + * @property {{ [_: string]: number } | null} GAXPtr - Soft reject + * @property {{ [_: string]: number } | null} GAY - Accept, hide + * @property {{ [_: string]: number } | null} GAYPtr - Soft reject + * @property {{ [_: string]: number } | null} GAZ - Accept, hide + * @property {{ [_: string]: number } | null} GAZPtr - Soft reject + * @property {{ [_: `${number}`]: number } | null} GACi - Accept, hide + * @property {{ [_: ComparableCstrAlias]: number } | null} GACV - Accept + * @property {{ [_: string]: number } | null} GACP - Reject + * @property {{ [_: string]: number } | null} GACiPtr - Reject + * @property {{ [_: string]: number } | null} GACVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GACPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GABi - Accept, hide + * @property {{ [_: BasicCstrAlias]: number } | null} GABs - Accept + * @property {{ [_: string]: number } | null} GABiPtr - Reject + * @property {{ [_: string]: number } | null} GABT - Reject + * @property {{ [_: string]: number } | null} GABTPtr - Reject + * @property {{ [_: GoodTildeCstrAlias]: number } | null} GAGT - Accept + * @property {{ [_: string]: number } | null} GAGTPtr - Accept, hide + * @property {{ [_: NonBasicCstrAlias]: number } | null} GANBV - Accept + * @property {{ [_: string]: number } | null} GANBP - Accept, hide + * @property {{ [_: string]: number } | null} GANBVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GANBPPtr - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV1 - Accept + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV2 - Accept + * @property {{ [_: string]: number } | null} GAPlP1 - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlP2 - Accept + * @property {{ [_: string]: number } | null} GAPlVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAPlPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GAMi - Accept, hide + * @property {{ [_: MixedCstrAlias]: number } | null} GAMS - Accept + * @property {{ [_: MixedCstrAlias]: number } | null} GAMV - Accept + * @property {{ [_: string]: number } | null} GAMSPtr - Reject + * @property {{ [_: string]: number } | null} GAMVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAII - Accept, hide + * @property {{ [_: InterfaceCstrAlias]: number } | null} GAIV - Accept + * @property {{ [_: string]: number } | null} GAIP - Accept, hide + * @property {{ [_: string]: number } | null} GAIIPtr - Reject + * @property {{ [_: string]: number } | null} GAIVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAIPPtr - Reject + * @property {{ [_: string]: number } | null} GAPrV - Accept, hide + * @property {{ [_: string]: number } | null} GAPrP - Accept, hide + * @property {{ [_: string]: number } | null} GAPrVPtr - Reject + * @property {{ [_: string]: number } | null} GAPrPPtr - Reject + */ + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..810db1875 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..0f2edd9c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,124 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * @typedef {$models.Data} Data + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {$models.ImplicitNonMarshaler} ImplicitNonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef {$models.NonMarshaler} NonMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..a956da60f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,186 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +/** + * @typedef {Object} Data + * @property {NonMarshaler} NM + * @property {NonMarshaler | null} NMPtr - NonMarshaler | null + * @property {ValueJsonMarshaler} VJM + * @property {ValueJsonMarshaler | null} VJMPtr - ValueJsonMarshaler | null + * @property {PointerJsonMarshaler} PJM + * @property {PointerJsonMarshaler | null} PJMPtr - PointerJsonMarshaler | null + * @property {ValueTextMarshaler} VTM + * @property {ValueTextMarshaler | null} VTMPtr - ValueTextMarshaler | null + * @property {PointerTextMarshaler} PTM + * @property {PointerTextMarshaler | null} PTMPtr - PointerTextMarshaler | null + * @property {ValueMarshaler} VM + * @property {ValueMarshaler | null} VMPtr - ValueMarshaler | null + * @property {PointerMarshaler} PM + * @property {PointerMarshaler | null} PMPtr - PointerMarshaler | null + * @property {UnderlyingJsonMarshaler} UJM + * @property {UnderlyingJsonMarshaler | null} UJMPtr - UnderlyingJsonMarshaler | null + * @property {UnderlyingTextMarshaler} UTM + * @property {UnderlyingTextMarshaler | null} UTMPtr - UnderlyingTextMarshaler | null + * @property {UnderlyingMarshaler} UM + * @property {UnderlyingMarshaler | null} UMPtr - UnderlyingMarshaler | null + * @property {any} JM - any + * @property {any | null} JMPtr - any | null + * @property {string} TM - string + * @property {string | null} TMPtr - string | null + * @property {any} CJM - any + * @property {any | null} CJMPtr - any | null + * @property {string} CTM - string + * @property {string | null} CTMPtr - string | null + * @property {any} CM - any + * @property {any | null} CMPtr - any | null + * @property {AliasNonMarshaler} ANM + * @property {AliasNonMarshaler | null} ANMPtr - AliasNonMarshaler | null + * @property {AliasJsonMarshaler} AJM + * @property {AliasJsonMarshaler | null} AJMPtr - AliasJsonMarshaler | null + * @property {AliasTextMarshaler} ATM + * @property {AliasTextMarshaler | null} ATMPtr - AliasTextMarshaler | null + * @property {AliasMarshaler} AM + * @property {AliasMarshaler | null} AMPtr - AliasMarshaler | null + * @property {ImplicitJsonMarshaler} ImJM + * @property {ImplicitJsonMarshaler | null} ImJMPtr - ImplicitJsonMarshaler | null + * @property {ImplicitTextMarshaler} ImTM + * @property {ImplicitTextMarshaler | null} ImTMPtr - ImplicitTextMarshaler | null + * @property {ImplicitMarshaler} ImM + * @property {ImplicitMarshaler | null} ImMPtr - ImplicitMarshaler | null + * @property {ImplicitNonJson} ImNJ + * @property {ImplicitNonJson | null} ImNJPtr - ImplicitNonJson | null + * @property {ImplicitNonText} ImNT + * @property {ImplicitNonText | null} ImNTPtr - ImplicitNonText | null + * @property {ImplicitNonMarshaler} ImNM + * @property {ImplicitNonMarshaler | null} ImNMPtr - ImplicitNonMarshaler | null + * @property {ImplicitJsonButText} ImJbT + * @property {ImplicitJsonButText | null} ImJbTPtr - ImplicitJsonButText | null + * @property {ImplicitTextButJson} ImTbJ + * @property {ImplicitTextButJson | null} ImTbJPtr - ImplicitTextButJson | null + */ + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {Object} ImplicitNonMarshaler + * @property {json$0.Marshaler} Marshaler + * @property {encoding$0.TextMarshaler} TextMarshaler + */ + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef { { + * } } NonMarshaler + */ + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..5ba9fe470 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..0b7f42650 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +import * as $models from "./models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {$models.HowDifferent} HowDifferent + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {$models.Impersonator} Impersonator + */ + +/** + * Person is not a number. + * @typedef {$models.Person} Person + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {$models.PrivatePerson} PrivatePerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..36f231303 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,44 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {Object} HowDifferent + * @property {string} Name - They have a name as well. + * @property {({ [_: string]: How } | null)[] | null} Differences - But they may have many differences. + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + * @typedef {Object} Person + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +/** + * @typedef {Object} personImpl + * @property {string} Nickname - Nickname conceals a person's identity. + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..33246d35e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +import * as $models from "./models.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {$models.OtherPerson} OtherPerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..63a2ee722 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {Object} OtherPerson + * @property {string} Name - They have a name as well. + * @property {T[] | null} Differences - But they may have many differences. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..d647deeb4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..dca6f4e6c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2124352079); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(4281222271); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..19bdcac29 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..f50558b8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByID(2590614085); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByID(773650321); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..3597b6460 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..b4f3f039c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..9343d5531 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..b4f3f039c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..41b452f57 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..7a4789f75 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(3568225479); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..ddd5e8916 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..ddd5e8916 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByID(3862002418, $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByID(2424639793, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByID(3132595881, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByID(3306292566, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByID(1754277916, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByID(1909469092, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByID(4251088558, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByID(1343888303, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByID(2205561041, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByID(572240879, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByID(2189402897, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByID(642881729, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByID(1066151743, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByID(2718999663, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByID(2386486356, $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByID(2163571325, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByID(2900172572, $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByID(881980169, $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByID(1075577233); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByID(3589606958, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByID(224675106, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByID(2124953624, $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByID(3516977899, $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByID(229603958, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByID(3678582682, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByID(319259595, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByID(383995060, $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByID(1091960237, $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByID(3835643147, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByID(2447692557, $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByID(2943477349, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByID(3401034892, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByID(1236957573, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByID(1160383782, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByID(1739300671, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByID(793803239, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByID(1403757716, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByID(2988345717, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByID(518250834, $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByID(2836661285, $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..6c1448d81 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..40d2245b9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..977600693 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..ab5cea255 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..3c9c56546 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByID(1491748400); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js new file mode 100644 index 000000000..cf48d86db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/index.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {$models.TextMarshaler} TextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js new file mode 100644 index 000000000..22f1fd904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/index.js @@ -0,0 +1,11 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {$models.Marshaler} Marshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js new file mode 100644 index 000000000..5dce4eea6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/json/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + * @typedef {any} Marshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js new file mode 100644 index 000000000..db89bafbc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/encoding/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + * @typedef {any} TextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js new file mode 100644 index 000000000..cc81267aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.js @@ -0,0 +1,70 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + * @param {$models.Alias} aliasValue + * @returns {$CancellablePromise<$models.Person>} + */ +export function Get(aliasValue) { + return $Call.ByName("main.GreetService.Get", aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + * @param {$models.AliasedPerson} p + * @returns {$CancellablePromise<$models.StrangelyAliasedPerson>} + */ +export function GetButAliased(p) { + return $Call.ByName("main.GreetService.GetButAliased", p); +} + +/** + * Get someone quite different. + * @returns {$CancellablePromise<$models.GenericPerson>} + */ +export function GetButDifferent() { + return $Call.ByName("main.GreetService.GetButDifferent"); +} + +/** + * @returns {$CancellablePromise} + */ +export function GetButForeignPrivateAlias() { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias"); +} + +/** + * @returns {$CancellablePromise<$models.AliasGroup>} + */ +export function GetButGenericAliases() { + return $Call.ByName("main.GreetService.GetButGenericAliases"); +} + +/** + * Greet a lot of unusual things. + * @param {$models.EmptyAliasStruct} $0 + * @param {$models.EmptyStruct} $1 + * @returns {$CancellablePromise<$models.AliasStruct>} + */ +export function Greet($0, $1) { + return $Call.ByName("main.GreetService.Greet", $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js new file mode 100644 index 000000000..5a5c62644 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.js @@ -0,0 +1,96 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * A nice type Alias. + * @typedef {$models.Alias} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {$models.AliasGroup} AliasGroup + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {$models.AliasStruct} AliasStruct + */ + +/** + * A class alias. + * @typedef {$models.AliasedPerson} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef {$models.EmptyAliasStruct} EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef {$models.EmptyStruct} EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {$models.GenericAlias} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {$models.GenericMapAlias} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {$models.GenericPerson} GenericPerson + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {$models.GenericPersonAlias} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {$models.GenericPtrAlias} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {$models.IndirectPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {$models.OtherAliasStruct} OtherAliasStruct + */ + +/** + * A non-generic struct containing an alias. + * @typedef {$models.Person} Person + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {$models.StrangelyAliasedPerson} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {$models.TPIndirectPersonAlias} TPIndirectPersonAlias + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js new file mode 100644 index 000000000..1ba2af395 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.js @@ -0,0 +1,112 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + * @typedef {number} Alias + */ + +/** + * A class whose fields have various aliased types. + * @typedef {Object} AliasGroup + * @property {GenericAlias} GAi + * @property {GenericAlias>} GAP + * @property {GenericPtrAlias} GPAs + * @property {GenericPtrAlias>} GPAP + * @property {GenericMapAlias} GMA + * @property {GenericPersonAlias} GPA + * @property {IndirectPersonAlias} IPA + * @property {TPIndirectPersonAlias} TPIPA + */ + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + * @typedef {Object} AliasStruct + * @property {number[] | null} Foo - A field with a comment. + * @property {string} [Bar] - Definitely not Foo. + * @property {string} [Baz] - Definitely not Foo. + * @property {OtherAliasStruct} Other - A nested alias struct. + */ + +/** + * A class alias. + * @typedef {Person} AliasedPerson + */ + +/** + * An empty struct alias. + * @typedef { { + * } } EmptyAliasStruct + */ + +/** + * An empty struct. + * @typedef { { + * } } EmptyStruct + */ + +/** + * A generic alias that forwards to a type parameter. + * @template T + * @typedef {T} GenericAlias + */ + +/** + * A generic alias that wraps a map. + * @template T,U + * @typedef {{ [_: string]: U } | null} GenericMapAlias + */ + +/** + * A generic struct containing an alias. + * @template T + * @typedef {Object} GenericPerson + * @property {T} Name + * @property {Alias} AliasedField + */ + +/** + * A generic alias that wraps a generic struct. + * @template T + * @typedef {GenericPerson[] | null>} GenericPersonAlias + */ + +/** + * A generic alias that wraps a pointer type. + * @template T + * @typedef {GenericAlias | null} GenericPtrAlias + */ + +/** + * An alias that wraps a class through a non-typeparam alias. + * @typedef {GenericPersonAlias} IndirectPersonAlias + */ + +/** + * Another struct alias. + * @typedef {Object} OtherAliasStruct + * @property {number[] | null} NoMoreIdeas + */ + +/** + * A non-generic struct containing an alias. + * @typedef {Object} Person + * @property {string} Name - The Person's name. + * @property {Alias} AliasedField - A random alias field. + */ + +/** + * Another class alias, but ordered after its aliased class. + * @typedef {Person} StrangelyAliasedPerson + */ + +/** + * An alias that wraps a class through a typeparam alias. + * @typedef {GenericAlias>} TPIndirectPersonAlias + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js new file mode 100644 index 000000000..f6c839720 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js new file mode 100644 index 000000000..f62552e8a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js new file mode 100644 index 000000000..244e74afe --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function TestMethod2() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js new file mode 100644 index 000000000..e146c3669 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {$models.Person} person + * @param {$models.Embedded1} emb + * @returns {$CancellablePromise} + */ +export function Greet(person, emb) { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js new file mode 100644 index 000000000..f21130b86 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Embedded1} Embedded1 + */ + +/** + * @typedef {$models.Embedded3} Embedded3 + */ + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js new file mode 100644 index 000000000..b30630777 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.js @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Embedded1 + * @property {number} Friends - Friends should be shadowed in Person by a field of lesser depth + * @property {number} Vanish - Vanish should be omitted from Person because there is another field with same depth and no tag + * @property {string} StillThere - StillThere should be shadowed in Person by other field with same depth and a json tag + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + */ + +/** + * @typedef {string} Embedded3 + */ + +/** + * Person represents a person + * @typedef { { + * "Titles"?: Title[] | null, + * "Names": string[] | null, + * "Partner": Person | null, + * "Friends": (Person | null)[] | null, + * "NamingThingsIsHard": `${boolean}`, + * "StillThere": Embedded3 | null, + * "-": number, + * "Embedded3": Embedded3, + * "StrangerNumber": `${number}`, + * "StrangestString"?: `"${string}"`, + * "StringStrangest"?: `"${string}"`, + * "emb4"?: embedded4, + * } } Person + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +/** + * @typedef {Object} embedded4 + * @property {`${boolean}`} NamingThingsIsHard - NamingThingsIsHard is a law of programming + * @property {boolean} Friends - Friends should not be shadowed in Person as embedded4 is not embedded from encoding/json's point of view; however, it should be shadowed in Embedded1 + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js new file mode 100644 index 000000000..03ef1f90f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + * @param {string} str + * @param {$models.Person[] | null} people + * @param {{"AnotherCount": number, "AnotherOne": $models.Person | null}} $2 + * @param {{ [_: `${number}`]: boolean | null } | null} assoc + * @param {(number | null)[] | null} $4 + * @param {string[]} other + * @returns {$CancellablePromise<[$models.Person, any, number[] | null]>} + */ +export function Greet(str, people, $2, assoc, $4, ...other) { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js new file mode 100644 index 000000000..88af1203b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js new file mode 100644 index 000000000..035bc0792 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.js @@ -0,0 +1,13 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + * @typedef {Object} Person + * @property {string} Name + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js new file mode 100644 index 000000000..8203e4168 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.StructA, $models.StructC]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js new file mode 100644 index 000000000..9b49a9172 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.js @@ -0,0 +1,22 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.StructA} StructA + */ + +/** + * @typedef {$models.StructC} StructC + */ + +/** + * @typedef {$models.StructE} StructE + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js new file mode 100644 index 000000000..50e0d0fc0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} StructA + * @property {structB | null} B + */ + +/** + * @typedef {Object} StructC + * @property {structD} D + */ + +/** + * @typedef { { + * } } StructE + */ + +/** + * @typedef {Object} structB + * @property {StructA | null} A + */ + +/** + * @typedef {Object} structD + * @property {StructE} E + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js new file mode 100644 index 000000000..623e88035 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + * @returns {$CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]>} + */ +export function MakeCycles() { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js new file mode 100644 index 000000000..9fc31bf7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.js @@ -0,0 +1,23 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Alias} Alias + */ + +/** + * @typedef {$models.Cyclic} Cyclic + */ + +/** + * @template T + * @typedef {$models.GenericCyclic} GenericCyclic + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js new file mode 100644 index 000000000..2413995ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Cyclic | null} Alias + */ + +/** + * @typedef {({ [_: string]: Alias } | null)[] | null} Cyclic + */ + +/** + * @template T + * @typedef {{"X": GenericCyclic | null, "Y": T[] | null}[] | null} GenericCyclic + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js new file mode 100644 index 000000000..40d68bf85 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello JS!"); +console.log("Hello JS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js new file mode 100644 index 000000000..374d13203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.InternalModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js new file mode 100644 index 000000000..a4c233c8c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + * @typedef {Object} InternalModel + * @property {string} Field + */ + +/** + * An unexported model. + * @typedef {Object} unexportedModel + * @property {string} Field + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js new file mode 100644 index 000000000..c93da8f05 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.js @@ -0,0 +1,9 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Dummy} Dummy + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js new file mode 100644 index 000000000..6b6f5401f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef { { + * } } Dummy + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js new file mode 100644 index 000000000..8332051eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +/** + * @param {string} $0 + * @returns {$CancellablePromise} + */ +function InternalMethod($0) { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +/** + * @param {otherpackage$0.Dummy} $0 + * @returns {$CancellablePromise} + */ +export function VisibleMethod($0) { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +/** + * @param {string} arg + * @returns {Promise} + */ +export async function CustomMethod(arg) { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js new file mode 100644 index 000000000..b2f9c5edb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_j.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js new file mode 100644 index 000000000..36e28f09b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ji.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("JS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js new file mode 100644 index 000000000..1fe8a0f69 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.js @@ -0,0 +1,24 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {$models.unexportedModel} $0 + * @returns {$CancellablePromise} + */ +export function Method($0) { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js new file mode 100644 index 000000000..3d245f10c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.js @@ -0,0 +1,53 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + * @returns {$CancellablePromise} + */ +export function Method1() { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + * @returns {$CancellablePromise} + */ +export function Method2() { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + * @returns {$CancellablePromise} + */ +export function Method3() { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + * @returns {$CancellablePromise} + */ +export function Method4() { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + * @returns {$CancellablePromise} + */ +export function Method5() { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js new file mode 100644 index 000000000..fa5b9a262 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.js @@ -0,0 +1,35 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {$models.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js new file mode 100644 index 000000000..649d8d016 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +import * as $models from "./models.js"; + +/** + * Person represents a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js new file mode 100644 index 000000000..e7c70729c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.js @@ -0,0 +1,61 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + * @typedef {Object} Person + * @property {Title} Title + * @property {string} Name + * @property {Age} Age + */ + +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js new file mode 100644 index 000000000..2f953de7c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.js @@ -0,0 +1,26 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @param {services$0.Title} title + * @returns {$CancellablePromise} + */ +export function Greet(name, title) { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js new file mode 100644 index 000000000..089a8b685 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js new file mode 100644 index 000000000..e0e2d3014 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js new file mode 100644 index 000000000..26922b7eb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js new file mode 100644 index 000000000..5743a9055 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js new file mode 100644 index 000000000..a27b0fea8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js new file mode 100644 index 000000000..8a9890617 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {other$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js new file mode 100644 index 000000000..98097e64f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js new file mode 100644 index 000000000..f89bfc417 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.js @@ -0,0 +1,30 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function GreetWithContext(name) { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js new file mode 100644 index 000000000..80fdcd24c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.js @@ -0,0 +1,98 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * @template S + * @typedef {$models.BasicCstrAlias} BasicCstrAlias + */ + +/** + * @template R + * @typedef {$models.ComparableCstrAlias} ComparableCstrAlias + */ + +/** + * @typedef {$models.EmbeddedCustomInterface} EmbeddedCustomInterface + */ + +/** + * @typedef {$models.EmbeddedOriginalInterface} EmbeddedOriginalInterface + */ + +/** + * @typedef {$models.EmbeddedPointer} EmbeddedPointer + */ + +/** + * @typedef {$models.EmbeddedPointerPtr} EmbeddedPointerPtr + */ + +/** + * @typedef {$models.EmbeddedValue} EmbeddedValue + */ + +/** + * @typedef {$models.EmbeddedValuePtr} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {$models.GoodTildeCstrAlias} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {$models.InterfaceCstrAlias} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {$models.Maps} Maps + */ + +/** + * @template X + * @typedef {$models.MixedCstrAlias} MixedCstrAlias + */ + +/** + * @template V + * @typedef {$models.NonBasicCstrAlias} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {$models.PointableCstrAlias} PointableCstrAlias + */ + +/** + * @typedef {$models.PointerAlias} PointerAlias + */ + +/** + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * @typedef {$models.StringAlias} StringAlias + */ + +/** + * @typedef {$models.StringType} StringType + */ + +/** + * @typedef {$models.ValueAlias} ValueAlias + */ + +/** + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js new file mode 100644 index 000000000..cee61a5e4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.js @@ -0,0 +1,240 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @template S + * @typedef {S} BasicCstrAlias + */ + +/** + * @template R + * @typedef {R} ComparableCstrAlias + */ + +/** + * @typedef {string} EmbeddedCustomInterface + */ + +/** + * @typedef {string} EmbeddedOriginalInterface + */ + +/** + * @typedef {string} EmbeddedPointer + */ + +/** + * @typedef {string} EmbeddedPointerPtr + */ + +/** + * @typedef {string} EmbeddedValue + */ + +/** + * @typedef {string} EmbeddedValuePtr + */ + +/** + * @template U + * @typedef {U} GoodTildeCstrAlias + */ + +/** + * @template Y + * @typedef {Y} InterfaceCstrAlias + */ + +/** + * @template R,S,T,U,V,W,X,Y,Z + * @typedef {Object} Maps + * @property {{ [_: string]: number } | null} Bool - Reject + * @property {{ [_: `${number}`]: number } | null} Int - Accept + * @property {{ [_: `${number}`]: number } | null} Uint - Accept + * @property {{ [_: string]: number } | null} Float - Reject + * @property {{ [_: string]: number } | null} Complex - Reject + * @property {{ [_: `${number}`]: number } | null} Byte - Accept + * @property {{ [_: `${number}`]: number } | null} Rune - Accept + * @property {{ [_: string]: number } | null} String - Accept + * @property {{ [_: string]: number } | null} IntPtr - Reject + * @property {{ [_: string]: number } | null} UintPtr - Reject + * @property {{ [_: string]: number } | null} FloatPtr - Reject + * @property {{ [_: string]: number } | null} ComplexPtr - Reject + * @property {{ [_: string]: number } | null} StringPtr - Reject + * @property {{ [_: string]: number } | null} NTM - Reject + * @property {{ [_: string]: number } | null} NTMPtr - Reject + * @property {{ [_: ValueTextMarshaler]: number } | null} VTM - Accept + * @property {{ [_: ValueTextMarshaler]: number } | null} VTMPtr - Accept + * @property {{ [_: string]: number } | null} PTM - Reject + * @property {{ [_: PointerTextMarshaler]: number } | null} PTMPtr - Accept + * @property {{ [_: string]: number } | null} JTM - Accept, hide + * @property {{ [_: string]: number } | null} JTMPtr - Accept, hide + * @property {{ [_: string]: number } | null} A - Reject + * @property {{ [_: string]: number } | null} APtr - Reject + * @property {{ [_: string]: number } | null} TM - Accept, hide + * @property {{ [_: string]: number } | null} TMPtr - Reject + * @property {{ [_: string]: number } | null} CI - Accept, hide + * @property {{ [_: string]: number } | null} CIPtr - Reject + * @property {{ [_: string]: number } | null} EI - Accept, hide + * @property {{ [_: string]: number } | null} EIPtr - Reject + * @property {{ [_: EmbeddedValue]: number } | null} EV - Accept + * @property {{ [_: EmbeddedValue]: number } | null} EVPtr - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVP - Accept + * @property {{ [_: EmbeddedValuePtr]: number } | null} EVPPtr - Accept + * @property {{ [_: string]: number } | null} EP - Reject + * @property {{ [_: EmbeddedPointer]: number } | null} EPPtr - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPP - Accept + * @property {{ [_: EmbeddedPointerPtr]: number } | null} EPPPtr - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECI - Accept + * @property {{ [_: EmbeddedCustomInterface]: number } | null} ECIPtr - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOI - Accept + * @property {{ [_: EmbeddedOriginalInterface]: number } | null} EOIPtr - Accept + * @property {{ [_: string]: number } | null} WT - Reject + * @property {{ [_: string]: number } | null} WA - Reject + * @property {{ [_: StringType]: number } | null} ST - Accept + * @property {{ [_: StringAlias]: number } | null} SA - Accept + * @property {{ [_: `${number}`]: number } | null} IntT - Accept + * @property {{ [_: `${number}`]: number } | null} IntA - Accept + * @property {{ [_: string]: number } | null} VT - Reject + * @property {{ [_: string]: number } | null} VTPtr - Reject + * @property {{ [_: string]: number } | null} VPT - Reject + * @property {{ [_: string]: number } | null} VPTPtr - Reject + * @property {{ [_: ValueAlias]: number } | null} VA - Accept + * @property {{ [_: ValueAlias]: number } | null} VAPtr - Accept + * @property {{ [_: string]: number } | null} VPA - Accept, hide + * @property {{ [_: string]: number } | null} VPAPtr - Reject + * @property {{ [_: string]: number } | null} PT - Reject + * @property {{ [_: string]: number } | null} PTPtr - Reject + * @property {{ [_: string]: number } | null} PPT - Reject + * @property {{ [_: string]: number } | null} PPTPtr - Reject + * @property {{ [_: string]: number } | null} PA - Reject + * @property {{ [_: PointerAlias]: number } | null} PAPtr - Accept + * @property {{ [_: string]: number } | null} PPA - Accept, hide + * @property {{ [_: string]: number } | null} PPAPtr - Reject + * @property {{ [_: string]: number } | null} IT - Accept, hide + * @property {{ [_: string]: number } | null} ITPtr - Reject + * @property {{ [_: string]: number } | null} IPT - Reject + * @property {{ [_: string]: number } | null} IPTPtr - Reject + * @property {{ [_: string]: number } | null} IA - Accept, hide + * @property {{ [_: string]: number } | null} IAPtr - Reject + * @property {{ [_: string]: number } | null} IPA - Reject + * @property {{ [_: string]: number } | null} IPAPtr - Reject + * @property {{ [_: string]: number } | null} TPR - Soft reject + * @property {{ [_: string]: number } | null} TPRPtr - Soft reject + * @property {{ [_: string]: number } | null} TPS - Accept, hide + * @property {{ [_: string]: number } | null} TPSPtr - Soft reject + * @property {{ [_: string]: number } | null} TPT - Soft reject + * @property {{ [_: string]: number } | null} TPTPtr - Soft reject + * @property {{ [_: string]: number } | null} TPU - Accept, hide + * @property {{ [_: string]: number } | null} TPUPtr - Soft reject + * @property {{ [_: string]: number } | null} TPV - Accept, hide + * @property {{ [_: string]: number } | null} TPVPtr - Soft reject + * @property {{ [_: string]: number } | null} TPW - Soft reject + * @property {{ [_: string]: number } | null} TPWPtr - Accept, hide + * @property {{ [_: string]: number } | null} TPX - Accept, hide + * @property {{ [_: string]: number } | null} TPXPtr - Soft reject + * @property {{ [_: string]: number } | null} TPY - Accept, hide + * @property {{ [_: string]: number } | null} TPYPtr - Soft reject + * @property {{ [_: string]: number } | null} TPZ - Accept, hide + * @property {{ [_: string]: number } | null} TPZPtr - Soft reject + * @property {{ [_: string]: number } | null} GAR - Soft reject + * @property {{ [_: string]: number } | null} GARPtr - Soft reject + * @property {{ [_: string]: number } | null} GAS - Accept, hide + * @property {{ [_: string]: number } | null} GASPtr - Soft reject + * @property {{ [_: string]: number } | null} GAT - Soft reject + * @property {{ [_: string]: number } | null} GATPtr - Soft reject + * @property {{ [_: string]: number } | null} GAU - Accept, hide + * @property {{ [_: string]: number } | null} GAUPtr - Soft reject + * @property {{ [_: string]: number } | null} GAV - Accept, hide + * @property {{ [_: string]: number } | null} GAVPtr - Soft reject + * @property {{ [_: string]: number } | null} GAW - Soft reject + * @property {{ [_: string]: number } | null} GAWPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAX - Accept, hide + * @property {{ [_: string]: number } | null} GAXPtr - Soft reject + * @property {{ [_: string]: number } | null} GAY - Accept, hide + * @property {{ [_: string]: number } | null} GAYPtr - Soft reject + * @property {{ [_: string]: number } | null} GAZ - Accept, hide + * @property {{ [_: string]: number } | null} GAZPtr - Soft reject + * @property {{ [_: `${number}`]: number } | null} GACi - Accept, hide + * @property {{ [_: ComparableCstrAlias]: number } | null} GACV - Accept + * @property {{ [_: string]: number } | null} GACP - Reject + * @property {{ [_: string]: number } | null} GACiPtr - Reject + * @property {{ [_: string]: number } | null} GACVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GACPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GABi - Accept, hide + * @property {{ [_: BasicCstrAlias]: number } | null} GABs - Accept + * @property {{ [_: string]: number } | null} GABiPtr - Reject + * @property {{ [_: string]: number } | null} GABT - Reject + * @property {{ [_: string]: number } | null} GABTPtr - Reject + * @property {{ [_: GoodTildeCstrAlias]: number } | null} GAGT - Accept + * @property {{ [_: string]: number } | null} GAGTPtr - Accept, hide + * @property {{ [_: NonBasicCstrAlias]: number } | null} GANBV - Accept + * @property {{ [_: string]: number } | null} GANBP - Accept, hide + * @property {{ [_: string]: number } | null} GANBVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GANBPPtr - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV1 - Accept + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlV2 - Accept + * @property {{ [_: string]: number } | null} GAPlP1 - Reject + * @property {{ [_: PointableCstrAlias]: number } | null} GAPlP2 - Accept + * @property {{ [_: string]: number } | null} GAPlVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAPlPPtr - Accept, hide + * @property {{ [_: `${number}`]: number } | null} GAMi - Accept, hide + * @property {{ [_: MixedCstrAlias]: number } | null} GAMS - Accept + * @property {{ [_: MixedCstrAlias]: number } | null} GAMV - Accept + * @property {{ [_: string]: number } | null} GAMSPtr - Reject + * @property {{ [_: string]: number } | null} GAMVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAII - Accept, hide + * @property {{ [_: InterfaceCstrAlias]: number } | null} GAIV - Accept + * @property {{ [_: string]: number } | null} GAIP - Accept, hide + * @property {{ [_: string]: number } | null} GAIIPtr - Reject + * @property {{ [_: string]: number } | null} GAIVPtr - Accept, hide + * @property {{ [_: string]: number } | null} GAIPPtr - Reject + * @property {{ [_: string]: number } | null} GAPrV - Accept, hide + * @property {{ [_: string]: number } | null} GAPrP - Accept, hide + * @property {{ [_: string]: number } | null} GAPrVPtr - Reject + * @property {{ [_: string]: number } | null} GAPrPPtr - Reject + */ + +/** + * @template X + * @typedef {X} MixedCstrAlias + */ + +/** + * @template V + * @typedef {V} NonBasicCstrAlias + */ + +/** + * @template W + * @typedef {W} PointableCstrAlias + */ + +/** + * @typedef {PointerTextMarshaler} PointerAlias + */ + +/** + * @typedef {string} PointerTextMarshaler + */ + +/** + * @typedef {string} StringAlias + */ + +/** + * @typedef {string} StringType + */ + +/** + * @typedef {ValueTextMarshaler} ValueAlias + */ + +/** + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js new file mode 100644 index 000000000..e207d968c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>>} + */ +export function Method() { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js new file mode 100644 index 000000000..0f2edd9c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.js @@ -0,0 +1,124 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +import * as $models from "./models.js"; + +/** + * any + * @typedef {$models.AliasJsonMarshaler} AliasJsonMarshaler + */ + +/** + * any + * @typedef {$models.AliasMarshaler} AliasMarshaler + */ + +/** + * struct{} + * @typedef {$models.AliasNonMarshaler} AliasNonMarshaler + */ + +/** + * string + * @typedef {$models.AliasTextMarshaler} AliasTextMarshaler + */ + +/** + * @typedef {$models.Data} Data + */ + +/** + * any + * @typedef {$models.ImplicitJsonButText} ImplicitJsonButText + */ + +/** + * any + * @typedef {$models.ImplicitJsonMarshaler} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitMarshaler} ImplicitMarshaler + */ + +/** + * string + * @typedef {$models.ImplicitNonJson} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {$models.ImplicitNonMarshaler} ImplicitNonMarshaler + */ + +/** + * any + * @typedef {$models.ImplicitNonText} ImplicitNonText + */ + +/** + * any + * @typedef {$models.ImplicitTextButJson} ImplicitTextButJson + */ + +/** + * string + * @typedef {$models.ImplicitTextMarshaler} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef {$models.NonMarshaler} NonMarshaler + */ + +/** + * any + * @typedef {$models.PointerJsonMarshaler} PointerJsonMarshaler + */ + +/** + * any + * @typedef {$models.PointerMarshaler} PointerMarshaler + */ + +/** + * string + * @typedef {$models.PointerTextMarshaler} PointerTextMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingJsonMarshaler} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {$models.UnderlyingMarshaler} UnderlyingMarshaler + */ + +/** + * string + * @typedef {$models.UnderlyingTextMarshaler} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {$models.ValueJsonMarshaler} ValueJsonMarshaler + */ + +/** + * any + * @typedef {$models.ValueMarshaler} ValueMarshaler + */ + +/** + * string + * @typedef {$models.ValueTextMarshaler} ValueTextMarshaler + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js new file mode 100644 index 000000000..a956da60f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.js @@ -0,0 +1,186 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + * @typedef {any} AliasJsonMarshaler + */ + +/** + * any + * @typedef {any} AliasMarshaler + */ + +/** + * struct{} + * @typedef { { + * } } AliasNonMarshaler + */ + +/** + * string + * @typedef {string} AliasTextMarshaler + */ + +/** + * @typedef {Object} Data + * @property {NonMarshaler} NM + * @property {NonMarshaler | null} NMPtr - NonMarshaler | null + * @property {ValueJsonMarshaler} VJM + * @property {ValueJsonMarshaler | null} VJMPtr - ValueJsonMarshaler | null + * @property {PointerJsonMarshaler} PJM + * @property {PointerJsonMarshaler | null} PJMPtr - PointerJsonMarshaler | null + * @property {ValueTextMarshaler} VTM + * @property {ValueTextMarshaler | null} VTMPtr - ValueTextMarshaler | null + * @property {PointerTextMarshaler} PTM + * @property {PointerTextMarshaler | null} PTMPtr - PointerTextMarshaler | null + * @property {ValueMarshaler} VM + * @property {ValueMarshaler | null} VMPtr - ValueMarshaler | null + * @property {PointerMarshaler} PM + * @property {PointerMarshaler | null} PMPtr - PointerMarshaler | null + * @property {UnderlyingJsonMarshaler} UJM + * @property {UnderlyingJsonMarshaler | null} UJMPtr - UnderlyingJsonMarshaler | null + * @property {UnderlyingTextMarshaler} UTM + * @property {UnderlyingTextMarshaler | null} UTMPtr - UnderlyingTextMarshaler | null + * @property {UnderlyingMarshaler} UM + * @property {UnderlyingMarshaler | null} UMPtr - UnderlyingMarshaler | null + * @property {any} JM - any + * @property {any | null} JMPtr - any | null + * @property {string} TM - string + * @property {string | null} TMPtr - string | null + * @property {any} CJM - any + * @property {any | null} CJMPtr - any | null + * @property {string} CTM - string + * @property {string | null} CTMPtr - string | null + * @property {any} CM - any + * @property {any | null} CMPtr - any | null + * @property {AliasNonMarshaler} ANM + * @property {AliasNonMarshaler | null} ANMPtr - AliasNonMarshaler | null + * @property {AliasJsonMarshaler} AJM + * @property {AliasJsonMarshaler | null} AJMPtr - AliasJsonMarshaler | null + * @property {AliasTextMarshaler} ATM + * @property {AliasTextMarshaler | null} ATMPtr - AliasTextMarshaler | null + * @property {AliasMarshaler} AM + * @property {AliasMarshaler | null} AMPtr - AliasMarshaler | null + * @property {ImplicitJsonMarshaler} ImJM + * @property {ImplicitJsonMarshaler | null} ImJMPtr - ImplicitJsonMarshaler | null + * @property {ImplicitTextMarshaler} ImTM + * @property {ImplicitTextMarshaler | null} ImTMPtr - ImplicitTextMarshaler | null + * @property {ImplicitMarshaler} ImM + * @property {ImplicitMarshaler | null} ImMPtr - ImplicitMarshaler | null + * @property {ImplicitNonJson} ImNJ + * @property {ImplicitNonJson | null} ImNJPtr - ImplicitNonJson | null + * @property {ImplicitNonText} ImNT + * @property {ImplicitNonText | null} ImNTPtr - ImplicitNonText | null + * @property {ImplicitNonMarshaler} ImNM + * @property {ImplicitNonMarshaler | null} ImNMPtr - ImplicitNonMarshaler | null + * @property {ImplicitJsonButText} ImJbT + * @property {ImplicitJsonButText | null} ImJbTPtr - ImplicitJsonButText | null + * @property {ImplicitTextButJson} ImTbJ + * @property {ImplicitTextButJson | null} ImTbJPtr - ImplicitTextButJson | null + */ + +/** + * any + * @typedef {any} ImplicitJsonButText + */ + +/** + * any + * @typedef {any} ImplicitJsonMarshaler + */ + +/** + * any + * @typedef {any} ImplicitMarshaler + */ + +/** + * string + * @typedef {string} ImplicitNonJson + */ + +/** + * class{ Marshaler, TextMarshaler } + * @typedef {Object} ImplicitNonMarshaler + * @property {json$0.Marshaler} Marshaler + * @property {encoding$0.TextMarshaler} TextMarshaler + */ + +/** + * any + * @typedef {any} ImplicitNonText + */ + +/** + * any + * @typedef {any} ImplicitTextButJson + */ + +/** + * string + * @typedef {string} ImplicitTextMarshaler + */ + +/** + * class {} + * @typedef { { + * } } NonMarshaler + */ + +/** + * any + * @typedef {any} PointerJsonMarshaler + */ + +/** + * any + * @typedef {any} PointerMarshaler + */ + +/** + * string + * @typedef {string} PointerTextMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingJsonMarshaler + */ + +/** + * any + * @typedef {any} UnderlyingMarshaler + */ + +/** + * string + * @typedef {string} UnderlyingTextMarshaler + */ + +/** + * any + * @typedef {any} ValueJsonMarshaler + */ + +/** + * any + * @typedef {any} ValueMarshaler + */ + +/** + * string + * @typedef {string} ValueTextMarshaler + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js new file mode 100644 index 000000000..9ad0c6d96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.js @@ -0,0 +1,18 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @returns {$CancellablePromise<$models.Data>} + */ +export function Method() { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js new file mode 100644 index 000000000..0b7f42650 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +import * as $models from "./models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {$models.HowDifferent} HowDifferent + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {$models.Impersonator} Impersonator + */ + +/** + * Person is not a number. + * @typedef {$models.Person} Person + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {$models.PrivatePerson} PrivatePerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js new file mode 100644 index 000000000..36f231303 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.js @@ -0,0 +1,44 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + * @template How + * @typedef {Object} HowDifferent + * @property {string} Name - They have a name as well. + * @property {({ [_: string]: How } | null)[] | null} Differences - But they may have many differences. + */ + +/** + * Impersonator gets their fields from other people. + * @typedef {other$0.OtherPerson} Impersonator + */ + +/** + * Person is not a number. + * @typedef {Object} Person + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +/** + * PrivatePerson gets their fields from hidden sources. + * @typedef {personImpl} PrivatePerson + */ + +/** + * @typedef {Object} personImpl + * @property {string} Nickname - Nickname conceals a person's identity. + * @property {string} Name - They have a name. + * @property {Impersonator[]} Friends - Exactly 4 sketchy friends. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js new file mode 100644 index 000000000..33246d35e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +import * as $models from "./models.js"; + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {$models.OtherPerson} OtherPerson + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js new file mode 100644 index 000000000..63a2ee722 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + * @template T + * @typedef {Object} OtherPerson + * @property {string} Name - They have a name as well. + * @property {T[] | null} Differences - But they may have many differences. + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js new file mode 100644 index 000000000..c3f8ff04b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js new file mode 100644 index 000000000..92435f679 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js new file mode 100644 index 000000000..490c12c08 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.js @@ -0,0 +1,20 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js new file mode 100644 index 000000000..5f940b2c7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + * @returns {$CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]>} + */ +export function LikeThisOne() { + return $Call.ByName("main.EmbedService.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + * @returns {$CancellablePromise} + */ +export function LikeThisOtherOne() { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js new file mode 100644 index 000000000..f8c6b19b2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} $0 + * @returns {$CancellablePromise} + */ +export function Greet($0) { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js new file mode 100644 index 000000000..734fb02e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.js @@ -0,0 +1,12 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js new file mode 100644 index 000000000..2fdca31cc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js new file mode 100644 index 000000000..e2ba84581 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.js @@ -0,0 +1,15 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js new file mode 100644 index 000000000..62ddbc166 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.js @@ -0,0 +1,10 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js new file mode 100644 index 000000000..2fdca31cc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * @returns {$CancellablePromise} + */ +export function Hello() { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js new file mode 100644 index 000000000..41b452f57 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.js @@ -0,0 +1,17 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js new file mode 100644 index 000000000..911d42560 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js new file mode 100644 index 000000000..eca08a018 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js new file mode 100644 index 000000000..eca08a018 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.js @@ -0,0 +1,360 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * @param {number[]} $in + * @returns {$CancellablePromise} + */ +export function ArrayInt($in) { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +/** + * @param {boolean} $in + * @returns {$CancellablePromise} + */ +export function BoolInBoolOut($in) { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float32InFloat32Out($in) { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Float64InFloat64Out($in) { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int16InIntOut($in) { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int32InIntOut($in) { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int64InIntOut($in) { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function Int8InIntOut($in) { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function Int8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function IntInIntOut($in) { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function IntPointerInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntInt($in) { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntIntPointer($in) { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise} + */ +export function MapIntSliceInt($in) { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +/** + * @param {{ [_: `${number}`]: number[] | null } | null} $in + * @returns {$CancellablePromise<{ [_: `${number}`]: number[] | null } | null>} + */ +export function MapIntSliceIntInMapIntSliceIntOut($in) { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +/** + * @returns {$CancellablePromise} + */ +export function NoInputsStringOut() { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +/** + * @param {boolean | null} $in + * @returns {$CancellablePromise} + */ +export function PointerBoolInBoolOut($in) { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat32InFloat32Out($in) { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function PointerFloat64InFloat64Out($in) { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +/** + * @param {{ [_: `${number}`]: number } | null} $in + * @returns {$CancellablePromise} + */ +export function PointerMapIntInt($in) { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +/** + * @param {string | null} $in + * @returns {$CancellablePromise} + */ +export function PointerStringInStringOut($in) { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutput($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputNamedOutputs($in) { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringArrayOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +/** + * @param {string[] | null} $in + * @returns {$CancellablePromise} + */ +export function StringArrayInputStringOut($in) { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +/** + * @param {$models.Person} $in + * @returns {$CancellablePromise<$models.Person>} + */ +export function StructInputStructOutput($in) { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise} + */ +export function StructPointerInputErrorOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +/** + * @param {$models.Person | null} $in + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function StructPointerInputStructPointerOutput($in) { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt16InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt16PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt32InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt32PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt64InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt64PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UInt8InUIntOut($in) { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UInt8PointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +/** + * @param {number} $in + * @returns {$CancellablePromise} + */ +export function UIntInUIntOut($in) { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +/** + * @param {number | null} $in + * @returns {$CancellablePromise} + */ +export function UIntPointerInAndOutput($in) { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js new file mode 100644 index 000000000..cb2979a7a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js new file mode 100644 index 000000000..ec6136e27 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Person + * @property {string} Name + * @property {Person | null} Parent + * @property {{"Age": number, "Address": {"Street": string}}} Details + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js new file mode 100644 index 000000000..bba7ea7ea --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.js @@ -0,0 +1,21 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js new file mode 100644 index 000000000..fdf1ff435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js new file mode 100644 index 000000000..8ab5f3462 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.js @@ -0,0 +1,34 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * @param {string} name + * @returns {$CancellablePromise} + */ +export function Greet(name) { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + * @param {string} name + * @returns {$CancellablePromise<$models.Person | null>} + */ +export function NewPerson(name) { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js new file mode 100644 index 000000000..977600693 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.js @@ -0,0 +1,16 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +import * as $models from "./models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {$models.Person} Person + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js new file mode 100644 index 000000000..ab5cea255 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.js @@ -0,0 +1,19 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + * @typedef {Object} Person + * @property {string} Name + * @property {services$0.Address | null} Address + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js new file mode 100644 index 000000000..588ef7ca7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +import * as $models from "./models.js"; + +/** + * @typedef {$models.Address} Address + */ diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js new file mode 100644 index 000000000..c04e3b10b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.js @@ -0,0 +1,14 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * @typedef {Object} Address + * @property {string} Street + * @property {string} State + * @property {string} Country + */ + +// In interface mode, this file is likely to contain just comments. +// We add a dummy export statement to ensure it is recognised as an ES module. +export {}; diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js new file mode 100644 index 000000000..6594edc5b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.js @@ -0,0 +1,25 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + * @returns {$CancellablePromise<$models.Address | null>} + */ +export function Yay() { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts new file mode 100644 index 000000000..8cd1a164f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/json/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts new file mode 100644 index 000000000..235dfce3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/encoding/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..3c6865881 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByID(1928502664, aliasValue).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByID(1896499664, p).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByID(2240931744).then(($result: any) => { + return $$createType1($result); + }); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByID(643456960).then(($result: any) => { + return $$createType2($result); + }); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByID(914093800).then(($result: any) => { + return $$createType3($result); + }); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByID(1411160069, $0, $1).then(($result: any) => { + return $$createType7($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..13f61da0f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +export type { + Alias, + AliasStruct, + EmptyAliasStruct, + GenericAlias, + GenericMapAlias, + GenericPtrAlias, + OtherAliasStruct +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..63ca43914 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,290 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; + + /** Creates a new AliasGroup instance. */ + constructor($$source: Partial = {}) { + if (!("GAi" in $$source)) { + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + */ + static createFrom($$source: any = {}): AliasGroup { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup($$parsedSource as Partial); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[]; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export class EmptyStruct { + + /** Creates a new EmptyStruct instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + */ + static createFrom($$source: any = {}): EmptyStruct { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct($$parsedSource as Partial); + } +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U }; + +/** + * A generic struct containing an alias. + */ +export class GenericPerson { + "Name"?: T; + "AliasedField": Alias; + + /** Creates a new GenericPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => GenericPerson { + const $$createField0_0 = $$createParamT; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson($$parsedSource as Partial>); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[]>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[]; +} + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..8fe0035ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..8260ce500 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..69c89433a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..6cdc52c66 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +export type { + Embedded3 +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..50f23b52d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,237 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** Creates a new Embedded1 instance. */ + constructor($$source: Partial = {}) { + if (!("Friends" in $$source)) { + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + */ + static createFrom($$source: any = {}): Embedded1 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1($$parsedSource as Partial); + } +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export class Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[]; + + /** + * Names has a + * multiline comment + */ + "Names": string[]; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[]; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Names" in $$source)) { + this["Names"] = []; + } + if (!("Partner" in $$source)) { + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = null; + } + if (!("-" in $$source)) { + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + this["StrangerNumber"] = "0"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export class embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; + + /** Creates a new embedded4 instance. */ + constructor($$source: Partial = {}) { + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + */ + static createFrom($$source: any = {}): embedded4 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..cc1e88ad7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,32 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[], $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null }, $4: (number | null)[], ...other: string[]): $CancellablePromise<[$models.Person, any, number[]]> { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..2417aff4c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + "Name": string; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..c1c70be69 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByID(440020721).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..4b190b8da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..9e86cd674 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,131 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + "B": structB | null; + + /** Creates a new StructA instance. */ + constructor($$source: Partial = {}) { + if (!("B" in $$source)) { + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + */ + static createFrom($$source: any = {}): StructA { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA($$parsedSource as Partial); + } +} + +export class StructC { + "D": structD; + + /** Creates a new StructC instance. */ + constructor($$source: Partial = {}) { + if (!("D" in $$source)) { + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + */ + static createFrom($$source: any = {}): StructC { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC($$parsedSource as Partial); + } +} + +export class StructE { + + /** Creates a new StructE instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + */ + static createFrom($$source: any = {}): StructE { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE($$parsedSource as Partial); + } +} + +export class structB { + "A": StructA | null; + + /** Creates a new structB instance. */ + constructor($$source: Partial = {}) { + if (!("A" in $$source)) { + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + */ + static createFrom($$source: any = {}): structB { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB($$parsedSource as Partial); + } +} + +export class structD { + "E": StructE; + + /** Creates a new structD instance. */ + constructor($$source: Partial = {}) { + if (!("E" in $$source)) { + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + */ + static createFrom($$source: any = {}): structD { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..cbddfb346 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,63 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByID(440020721).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + }); +} + +// Private type creation functions +var $$createType0 = (function $$initCreateType0(...args: any[]): any { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = (function $$initCreateType4(...args: any[]): any { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = (function $$initCreateType9(...args: any[]): any { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..93d1ef5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type Alias = Cyclic | null; + +export type Cyclic = { [_: string]: Alias }[]; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[]}[]; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..b9fbdba96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello TS!"); +console.log("Hello TS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..b968330db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..4d242fc2c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,54 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + "Field": string; + + /** Creates a new InternalModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + */ + static createFrom($$source: any = {}): InternalModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel($$parsedSource as Partial); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + "Field": string; + + /** Creates a new unexportedModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + */ + static createFrom($$source: any = {}): unexportedModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..e52a0ccb8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..b927155d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + + /** Creates a new Dummy instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + */ + static createFrom($$source: any = {}): Dummy { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..338c7fbd1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByID(3518775569, $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByID(474018228, $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..a819daffd --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..b5d189f76 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..10de8838c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..3b4d2f5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..a50282a38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..3bce7b000 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..661222bdf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..f1ff262a1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..8dc381545 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2007737399).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..88b2c78db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + "Name": string; + "Address": other$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..fe69a1da0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2447353446).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..386913d65 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..b5890af28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..0d26a6b0b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,1886 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export class Maps { + /** + * Reject + */ + "Bool": { [_: string]: number }; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number }; + + /** + * Reject + */ + "Float": { [_: string]: number }; + + /** + * Reject + */ + "Complex": { [_: string]: number }; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number }; + + /** + * Accept + */ + "String": { [_: string]: number }; + + /** + * Reject + */ + "IntPtr": { [_: string]: number }; + + /** + * Reject + */ + "UintPtr": { [_: string]: number }; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number }; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number }; + + /** + * Reject + */ + "StringPtr": { [_: string]: number }; + + /** + * Reject + */ + "NTM": { [_: string]: number }; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number }; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number }; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number }; + + /** + * Reject + */ + "PTM": { [_: string]: number }; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number }; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number }; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number }; + + /** + * Reject + */ + "A": { [_: string]: number }; + + /** + * Reject + */ + "APtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TM": { [_: string]: number }; + + /** + * Reject + */ + "TMPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "CI": { [_: string]: number }; + + /** + * Reject + */ + "CIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "EI": { [_: string]: number }; + + /** + * Reject + */ + "EIPtr": { [_: string]: number }; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number }; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number }; + + /** + * Reject + */ + "EP": { [_: string]: number }; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number }; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Reject + */ + "WT": { [_: string]: number }; + + /** + * Reject + */ + "WA": { [_: string]: number }; + + /** + * Accept + */ + "ST": { [_: StringType]: number }; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number }; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number }; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number }; + + /** + * Reject + */ + "VT": { [_: string]: number }; + + /** + * Reject + */ + "VTPtr": { [_: string]: number }; + + /** + * Reject + */ + "VPT": { [_: string]: number }; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number }; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number }; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number }; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number }; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number }; + + /** + * Reject + */ + "PT": { [_: string]: number }; + + /** + * Reject + */ + "PTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PPT": { [_: string]: number }; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PA": { [_: string]: number }; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number }; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number }; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IT": { [_: string]: number }; + + /** + * Reject + */ + "ITPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPT": { [_: string]: number }; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IA": { [_: string]: number }; + + /** + * Reject + */ + "IAPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPA": { [_: string]: number }; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPR": { [_: string]: number }; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number }; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPT": { [_: string]: number }; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number }; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number }; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPW": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number }; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number }; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number }; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAR": { [_: string]: number }; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number }; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAT": { [_: string]: number }; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number }; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number }; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAW": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number }; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number }; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number }; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number }; + + /** + * Reject + */ + "GACP": { [_: string]: number }; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number }; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number }; + + /** + * Reject + */ + "GABT": { [_: string]: number }; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number }; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number }; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number }; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number }; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number }; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number }; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number }; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number }; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number }; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number }; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number }; + + /** Creates a new Maps instance. */ + constructor($$source: Partial> = {}) { + if (!("Bool" in $$source)) { + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + this["Rune"] = {}; + } + if (!("String" in $$source)) { + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + this["A"] = {}; + } + if (!("APtr" in $$source)) { + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + this["WT"] = {}; + } + if (!("WA" in $$source)) { + this["WA"] = {}; + } + if (!("ST" in $$source)) { + this["ST"] = {}; + } + if (!("SA" in $$source)) { + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + */ + static createFrom($$createParamR: (source: any) => R, $$createParamS: (source: any) => S, $$createParamT: (source: any) => T, $$createParamU: (source: any) => U, $$createParamV: (source: any) => V, $$createParamW: (source: any) => W, $$createParamX: (source: any) => X, $$createParamY: (source: any) => Y, $$createParamZ: (source: any) => Z): ($$source?: any) => Maps { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps($$parsedSource as Partial>); + }; + } +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType60 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType61 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType62 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType63 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType64 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType65 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType66 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType67 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType68 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType69 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType70 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType71 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType72 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType73 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType74 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType75 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType76 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..bd5e88c7b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByID(4021345184).then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..bbe49e32f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,36 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..d8db23862 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,545 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export class Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; + + /** Creates a new Data instance. */ + constructor($$source: Partial = {}) { + if (!("NM" in $$source)) { + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + */ + static createFrom($$source: any = {}): Data { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; + + /** Creates a new ImplicitNonMarshaler instance. */ + constructor($$source: Partial = {}) { + if (!("Marshaler" in $$source)) { + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): ImplicitNonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export class NonMarshaler { + + /** Creates a new NonMarshaler instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): NonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..342fcf3c4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByID(4021345184).then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..19d990dbf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..4cb328e1e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,163 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export class HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": { [_: string]: How }[]; + + /** Creates a new HowDifferent instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + */ + static createFrom($$createParamHow: (source: any) => How): ($$source?: any) => HowDifferent { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent($$parsedSource as Partial>); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export class Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person($$parsedSource as Partial); + } +} + +export class personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new personImpl instance. */ + constructor($$source: Partial = {}) { + if (!("Nickname" in $$source)) { + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + */ + static createFrom($$source: any = {}): personImpl { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl($$parsedSource as Partial); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +// Private type creation functions +const $$createType0 = ($$createParamHow: any) => $Create.Map($Create.Any, $$createParamHow); +const $$createType1 = ($$createParamHow: any) => $Create.Array($$createType0($$createParamHow)); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..d2d973b28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..711735a2b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,52 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + */ +export class OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[]; + + /** Creates a new OtherPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => OtherPerson { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson($$parsedSource as Partial>); + }; + } +} + +// Private type creation functions +const $$createType0 = ($$createParamT: any) => $Create.Array($$createParamT); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..da11f7f2f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..9ef257343 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByID(2124352079).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(4281222271); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..a5fe5368f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..615eae691 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByID(2590614085).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(773650321); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..83ca81acc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..6c902629c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..62283209b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..6c902629c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..d76f68d9d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..b8c293ad7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(3568225479).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..c8ff6e4db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByID(881980169, $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByID(3678582682, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByID(319259595, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByID(383995060, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..c8ff6e4db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByID(881980169, $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByID(3678582682, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByID(319259595, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByID(383995060, $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..6e6ac2007 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..88cafa9d1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f6eee9de8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..ec098d45a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(1491748400).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts new file mode 100644 index 000000000..8cd1a164f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/json/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts new file mode 100644 index 000000000..235dfce3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/encoding/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..b33d68383 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.Get", aliasValue).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByName("main.GreetService.GetButAliased", p).then(($result: any) => { + return $$createType0($result); + }); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByName("main.GreetService.GetButDifferent").then(($result: any) => { + return $$createType1($result); + }); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias").then(($result: any) => { + return $$createType2($result); + }); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByName("main.GreetService.GetButGenericAliases").then(($result: any) => { + return $$createType3($result); + }); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByName("main.GreetService.Greet", $0, $1).then(($result: any) => { + return $$createType7($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.GenericPerson.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; +const $$createType3 = $models.AliasGroup.createFrom; +const $$createType4 = $Create.Array($Create.Any); +const $$createType5 = $Create.Array($Create.Any); +const $$createType6 = $Create.Struct({ + "NoMoreIdeas": $$createType5, +}); +const $$createType7 = $Create.Struct({ + "Foo": $$createType4, + "Other": $$createType6, +}); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..13f61da0f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + AliasGroup, + AliasedPerson, + EmptyStruct, + GenericPerson, + GenericPersonAlias, + IndirectPersonAlias, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; + +export type { + Alias, + AliasStruct, + EmptyAliasStruct, + GenericAlias, + GenericMapAlias, + GenericPtrAlias, + OtherAliasStruct +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..63ca43914 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,290 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export class AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; + + /** Creates a new AliasGroup instance. */ + constructor($$source: Partial = {}) { + if (!("GAi" in $$source)) { + this["GAi"] = 0; + } + if (!("GAP" in $$source)) { + this["GAP"] = (new GenericPerson()); + } + if (!("GPAs" in $$source)) { + this["GPAs"] = null; + } + if (!("GPAP" in $$source)) { + this["GPAP"] = null; + } + if (!("GMA" in $$source)) { + this["GMA"] = {}; + } + if (!("GPA" in $$source)) { + this["GPA"] = (new GenericPersonAlias()); + } + if (!("IPA" in $$source)) { + this["IPA"] = (new IndirectPersonAlias()); + } + if (!("TPIPA" in $$source)) { + this["TPIPA"] = (new TPIndirectPersonAlias()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new AliasGroup instance from a string or object. + */ + static createFrom($$source: any = {}): AliasGroup { + const $$createField1_0 = $$createType0; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType5; + const $$createField4_0 = $$createType6; + const $$createField5_0 = $$createType8; + const $$createField6_0 = $$createType8; + const $$createField7_0 = $$createType0; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("GAP" in $$parsedSource) { + $$parsedSource["GAP"] = $$createField1_0($$parsedSource["GAP"]); + } + if ("GPAs" in $$parsedSource) { + $$parsedSource["GPAs"] = $$createField2_0($$parsedSource["GPAs"]); + } + if ("GPAP" in $$parsedSource) { + $$parsedSource["GPAP"] = $$createField3_0($$parsedSource["GPAP"]); + } + if ("GMA" in $$parsedSource) { + $$parsedSource["GMA"] = $$createField4_0($$parsedSource["GMA"]); + } + if ("GPA" in $$parsedSource) { + $$parsedSource["GPA"] = $$createField5_0($$parsedSource["GPA"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField6_0($$parsedSource["IPA"]); + } + if ("TPIPA" in $$parsedSource) { + $$parsedSource["TPIPA"] = $$createField7_0($$parsedSource["TPIPA"]); + } + return new AliasGroup($$parsedSource as Partial); + } +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[]; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export class EmptyStruct { + + /** Creates a new EmptyStruct instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EmptyStruct instance from a string or object. + */ + static createFrom($$source: any = {}): EmptyStruct { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EmptyStruct($$parsedSource as Partial); + } +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U }; + +/** + * A generic struct containing an alias. + */ +export class GenericPerson { + "Name"?: T; + "AliasedField": Alias; + + /** Creates a new GenericPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class GenericPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => GenericPerson { + const $$createField0_0 = $$createParamT; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Name" in $$parsedSource) { + $$parsedSource["Name"] = $$createField0_0($$parsedSource["Name"]); + } + return new GenericPerson($$parsedSource as Partial>); + }; + } +} + +/** + * A generic alias that wraps a generic struct. + */ +export const GenericPersonAlias = GenericPerson; + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[]>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export const IndirectPersonAlias = GenericPersonAlias; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[]; +} + +/** + * A non-generic struct containing an alias. + */ +export class Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("AliasedField" in $$source)) { + this["AliasedField"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * A class alias. + */ +export const AliasedPerson = Person; + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export const StrangelyAliasedPerson = Person; + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export const TPIndirectPersonAlias = GenericPerson; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; + +// Private type creation functions +const $$createType0 = GenericPerson.createFrom($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = $Create.Nullable($$createType1); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = GenericPerson.createFrom($$createType3); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Array($Create.Any); +const $$createType8 = GenericPerson.createFrom($$createType7); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..0780a0de3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..a2222265f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..f2106e7f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..6cdc52c66 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Embedded1, + Person, + Title +} from "./models.js"; + +export type { + Embedded3 +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..50f23b52d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,237 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** Creates a new Embedded1 instance. */ + constructor($$source: Partial = {}) { + if (!("Friends" in $$source)) { + this["Friends"] = 0; + } + if (!("Vanish" in $$source)) { + this["Vanish"] = 0; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = ""; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Embedded1 instance from a string or object. + */ + static createFrom($$source: any = {}): Embedded1 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Embedded1($$parsedSource as Partial); + } +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export class Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[]; + + /** + * Names has a + * multiline comment + */ + "Names": string[]; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[]; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Names" in $$source)) { + this["Names"] = []; + } + if (!("Partner" in $$source)) { + this["Partner"] = null; + } + if (!("Friends" in $$source)) { + this["Friends"] = []; + } + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("StillThere" in $$source)) { + this["StillThere"] = null; + } + if (!("-" in $$source)) { + this["-"] = 0; + } + if (!("Embedded3" in $$source)) { + this["Embedded3"] = ""; + } + if (!("StrangerNumber" in $$source)) { + this["StrangerNumber"] = "0"; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType3; + const $$createField3_0 = $$createType4; + const $$createField11_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Titles" in $$parsedSource) { + $$parsedSource["Titles"] = $$createField0_0($$parsedSource["Titles"]); + } + if ("Names" in $$parsedSource) { + $$parsedSource["Names"] = $$createField1_0($$parsedSource["Names"]); + } + if ("Partner" in $$parsedSource) { + $$parsedSource["Partner"] = $$createField2_0($$parsedSource["Partner"]); + } + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField3_0($$parsedSource["Friends"]); + } + if ("emb4" in $$parsedSource) { + $$parsedSource["emb4"] = $$createField11_0($$parsedSource["emb4"]); + } + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export class embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; + + /** Creates a new embedded4 instance. */ + constructor($$source: Partial = {}) { + if (!("NamingThingsIsHard" in $$source)) { + this["NamingThingsIsHard"] = "false"; + } + if (!("Friends" in $$source)) { + this["Friends"] = false; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new embedded4 instance from a string or object. + */ + static createFrom($$source: any = {}): embedded4 { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new embedded4($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Array($Create.Any); +const $$createType2 = Person.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = $Create.Array($$createType3); +const $$createType5 = embedded4.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..4471270ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,32 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[], $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null }, $4: (number | null)[], ...other: string[]): $CancellablePromise<[$models.Person, any, number[]]> { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other).then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[2] = $$createType1($result[2]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Array($Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..2417aff4c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Person represents a person + */ +export class Person { + "Name": string; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..2f0e45aff --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByName("main.GreetService.MakeCycles").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + return $result; + }); +} + +// Private type creation functions +const $$createType0 = $models.StructA.createFrom; +const $$createType1 = $models.StructC.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..4b190b8da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..9e86cd674 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,131 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class StructA { + "B": structB | null; + + /** Creates a new StructA instance. */ + constructor($$source: Partial = {}) { + if (!("B" in $$source)) { + this["B"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructA instance from a string or object. + */ + static createFrom($$source: any = {}): StructA { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("B" in $$parsedSource) { + $$parsedSource["B"] = $$createField0_0($$parsedSource["B"]); + } + return new StructA($$parsedSource as Partial); + } +} + +export class StructC { + "D": structD; + + /** Creates a new StructC instance. */ + constructor($$source: Partial = {}) { + if (!("D" in $$source)) { + this["D"] = (new structD()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new StructC instance from a string or object. + */ + static createFrom($$source: any = {}): StructC { + const $$createField0_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("D" in $$parsedSource) { + $$parsedSource["D"] = $$createField0_0($$parsedSource["D"]); + } + return new StructC($$parsedSource as Partial); + } +} + +export class StructE { + + /** Creates a new StructE instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new StructE instance from a string or object. + */ + static createFrom($$source: any = {}): StructE { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new StructE($$parsedSource as Partial); + } +} + +export class structB { + "A": StructA | null; + + /** Creates a new structB instance. */ + constructor($$source: Partial = {}) { + if (!("A" in $$source)) { + this["A"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structB instance from a string or object. + */ + static createFrom($$source: any = {}): structB { + const $$createField0_0 = $$createType4; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField0_0($$parsedSource["A"]); + } + return new structB($$parsedSource as Partial); + } +} + +export class structD { + "E": StructE; + + /** Creates a new structD instance. */ + constructor($$source: Partial = {}) { + if (!("E" in $$source)) { + this["E"] = (new StructE()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new structD instance from a string or object. + */ + static createFrom($$source: any = {}): structD { + const $$createField0_0 = $$createType5; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("E" in $$parsedSource) { + $$parsedSource["E"] = $$createField0_0($$parsedSource["E"]); + } + return new structD($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = structB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = structD.createFrom; +const $$createType3 = StructA.createFrom; +const $$createType4 = $Create.Nullable($$createType3); +const $$createType5 = StructE.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..dba844168 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,63 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByName("main.GreetService.MakeCycles").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType9($result[1]); + return $result; + }); +} + +// Private type creation functions +var $$createType0 = (function $$initCreateType0(...args: any[]): any { + if ($$createType0 === $$initCreateType0) { + $$createType0 = $$createType3; + } + return $$createType0(...args); +}); +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Map($Create.Any, $$createType1); +const $$createType3 = $Create.Array($$createType2); +var $$createType4 = (function $$initCreateType4(...args: any[]): any { + if ($$createType4 === $$initCreateType4) { + $$createType4 = $$createType8; + } + return $$createType4(...args); +}); +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); +const $$createType7 = $Create.Struct({ + "X": $$createType5, + "Y": $$createType6, +}); +const $$createType8 = $Create.Array($$createType7); +var $$createType9 = (function $$initCreateType9(...args: any[]): any { + if ($$createType9 === $$initCreateType9) { + $$createType9 = $$createType13; + } + return $$createType9(...args); +}); +const $$createType10 = $Create.Nullable($$createType9); +const $$createType11 = $Create.Array($$createType4); +const $$createType12 = $Create.Struct({ + "X": $$createType10, + "Y": $$createType11, +}); +const $$createType13 = $Create.Array($$createType12); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..93d1ef5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,12 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type Alias = Cyclic | null; + +export type Cyclic = { [_: string]: Alias }[]; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[]}[]; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..b9fbdba96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Classes!"); +console.log("Hello TS!"); +console.log("Hello TS Classes!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..9271273bc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..4d242fc2c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,54 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * An exported but internal model. + */ +export class InternalModel { + "Field": string; + + /** Creates a new InternalModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new InternalModel instance from a string or object. + */ + static createFrom($$source: any = {}): InternalModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new InternalModel($$parsedSource as Partial); + } +} + +/** + * An unexported model. + */ +export class unexportedModel { + "Field": string; + + /** Creates a new unexportedModel instance. */ + constructor($$source: Partial = {}) { + if (!("Field" in $$source)) { + this["Field"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new unexportedModel instance from a string or object. + */ + static createFrom($$source: any = {}): unexportedModel { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new unexportedModel($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..e52a0ccb8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..b927155d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Dummy { + + /** Creates a new Dummy instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Dummy instance from a string or object. + */ + static createFrom($$source: any = {}): Dummy { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Dummy($$parsedSource as Partial); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts new file mode 100644 index 000000000..6703820f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts new file mode 100644 index 000000000..15d2994e9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "../service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..0687bd8ac --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js new file mode 100644 index 000000000..724e79e12 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_c.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts new file mode 100644 index 000000000..66b739d3a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_tc.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Classes"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..34b23e24d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..c5b06be75 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..45abc153e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..3b4d2f5c6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Person, + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..a50282a38 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,82 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Person($$parsedSource as Partial); + } +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..c4afd85da --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..661222bdf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..f1ff262a1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,46 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..f44f172e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..88b2c78db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export class Person { + "Name": string; + "Address": other$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = other$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..fa9a93fc8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..aec011527 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..b5890af28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Maps +} from "./models.js"; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..0d26a6b0b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,1886 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export class Maps { + /** + * Reject + */ + "Bool": { [_: string]: number }; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number }; + + /** + * Reject + */ + "Float": { [_: string]: number }; + + /** + * Reject + */ + "Complex": { [_: string]: number }; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number }; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number }; + + /** + * Accept + */ + "String": { [_: string]: number }; + + /** + * Reject + */ + "IntPtr": { [_: string]: number }; + + /** + * Reject + */ + "UintPtr": { [_: string]: number }; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number }; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number }; + + /** + * Reject + */ + "StringPtr": { [_: string]: number }; + + /** + * Reject + */ + "NTM": { [_: string]: number }; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number }; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number }; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number }; + + /** + * Reject + */ + "PTM": { [_: string]: number }; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number }; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number }; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number }; + + /** + * Reject + */ + "A": { [_: string]: number }; + + /** + * Reject + */ + "APtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TM": { [_: string]: number }; + + /** + * Reject + */ + "TMPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "CI": { [_: string]: number }; + + /** + * Reject + */ + "CIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "EI": { [_: string]: number }; + + /** + * Reject + */ + "EIPtr": { [_: string]: number }; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number }; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number }; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number }; + + /** + * Reject + */ + "EP": { [_: string]: number }; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number }; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number }; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number }; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number }; + + /** + * Reject + */ + "WT": { [_: string]: number }; + + /** + * Reject + */ + "WA": { [_: string]: number }; + + /** + * Accept + */ + "ST": { [_: StringType]: number }; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number }; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number }; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number }; + + /** + * Reject + */ + "VT": { [_: string]: number }; + + /** + * Reject + */ + "VTPtr": { [_: string]: number }; + + /** + * Reject + */ + "VPT": { [_: string]: number }; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number }; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number }; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number }; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number }; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number }; + + /** + * Reject + */ + "PT": { [_: string]: number }; + + /** + * Reject + */ + "PTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PPT": { [_: string]: number }; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number }; + + /** + * Reject + */ + "PA": { [_: string]: number }; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number }; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number }; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IT": { [_: string]: number }; + + /** + * Reject + */ + "ITPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPT": { [_: string]: number }; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "IA": { [_: string]: number }; + + /** + * Reject + */ + "IAPtr": { [_: string]: number }; + + /** + * Reject + */ + "IPA": { [_: string]: number }; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPR": { [_: string]: number }; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number }; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPT": { [_: string]: number }; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number }; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number }; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "TPW": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number }; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number }; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number }; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAR": { [_: string]: number }; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number }; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAT": { [_: string]: number }; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number }; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number }; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number }; + + /** + * Soft reject + */ + "GAW": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number }; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number }; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number }; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number }; + + /** + * Reject + */ + "GACP": { [_: string]: number }; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number }; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number }; + + /** + * Reject + */ + "GABT": { [_: string]: number }; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number }; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number }; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number }; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number }; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number }; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number }; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number }; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number }; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number }; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number }; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number }; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number }; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number }; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number }; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number }; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number }; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number }; + + /** Creates a new Maps instance. */ + constructor($$source: Partial> = {}) { + if (!("Bool" in $$source)) { + this["Bool"] = {}; + } + if (!("Int" in $$source)) { + this["Int"] = {}; + } + if (!("Uint" in $$source)) { + this["Uint"] = {}; + } + if (!("Float" in $$source)) { + this["Float"] = {}; + } + if (!("Complex" in $$source)) { + this["Complex"] = {}; + } + if (!("Byte" in $$source)) { + this["Byte"] = {}; + } + if (!("Rune" in $$source)) { + this["Rune"] = {}; + } + if (!("String" in $$source)) { + this["String"] = {}; + } + if (!("IntPtr" in $$source)) { + this["IntPtr"] = {}; + } + if (!("UintPtr" in $$source)) { + this["UintPtr"] = {}; + } + if (!("FloatPtr" in $$source)) { + this["FloatPtr"] = {}; + } + if (!("ComplexPtr" in $$source)) { + this["ComplexPtr"] = {}; + } + if (!("StringPtr" in $$source)) { + this["StringPtr"] = {}; + } + if (!("NTM" in $$source)) { + this["NTM"] = {}; + } + if (!("NTMPtr" in $$source)) { + this["NTMPtr"] = {}; + } + if (!("VTM" in $$source)) { + this["VTM"] = {}; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = {}; + } + if (!("PTM" in $$source)) { + this["PTM"] = {}; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = {}; + } + if (!("JTM" in $$source)) { + this["JTM"] = {}; + } + if (!("JTMPtr" in $$source)) { + this["JTMPtr"] = {}; + } + if (!("A" in $$source)) { + this["A"] = {}; + } + if (!("APtr" in $$source)) { + this["APtr"] = {}; + } + if (!("TM" in $$source)) { + this["TM"] = {}; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = {}; + } + if (!("CI" in $$source)) { + this["CI"] = {}; + } + if (!("CIPtr" in $$source)) { + this["CIPtr"] = {}; + } + if (!("EI" in $$source)) { + this["EI"] = {}; + } + if (!("EIPtr" in $$source)) { + this["EIPtr"] = {}; + } + if (!("EV" in $$source)) { + this["EV"] = {}; + } + if (!("EVPtr" in $$source)) { + this["EVPtr"] = {}; + } + if (!("EVP" in $$source)) { + this["EVP"] = {}; + } + if (!("EVPPtr" in $$source)) { + this["EVPPtr"] = {}; + } + if (!("EP" in $$source)) { + this["EP"] = {}; + } + if (!("EPPtr" in $$source)) { + this["EPPtr"] = {}; + } + if (!("EPP" in $$source)) { + this["EPP"] = {}; + } + if (!("EPPPtr" in $$source)) { + this["EPPPtr"] = {}; + } + if (!("ECI" in $$source)) { + this["ECI"] = {}; + } + if (!("ECIPtr" in $$source)) { + this["ECIPtr"] = {}; + } + if (!("EOI" in $$source)) { + this["EOI"] = {}; + } + if (!("EOIPtr" in $$source)) { + this["EOIPtr"] = {}; + } + if (!("WT" in $$source)) { + this["WT"] = {}; + } + if (!("WA" in $$source)) { + this["WA"] = {}; + } + if (!("ST" in $$source)) { + this["ST"] = {}; + } + if (!("SA" in $$source)) { + this["SA"] = {}; + } + if (!("IntT" in $$source)) { + this["IntT"] = {}; + } + if (!("IntA" in $$source)) { + this["IntA"] = {}; + } + if (!("VT" in $$source)) { + this["VT"] = {}; + } + if (!("VTPtr" in $$source)) { + this["VTPtr"] = {}; + } + if (!("VPT" in $$source)) { + this["VPT"] = {}; + } + if (!("VPTPtr" in $$source)) { + this["VPTPtr"] = {}; + } + if (!("VA" in $$source)) { + this["VA"] = {}; + } + if (!("VAPtr" in $$source)) { + this["VAPtr"] = {}; + } + if (!("VPA" in $$source)) { + this["VPA"] = {}; + } + if (!("VPAPtr" in $$source)) { + this["VPAPtr"] = {}; + } + if (!("PT" in $$source)) { + this["PT"] = {}; + } + if (!("PTPtr" in $$source)) { + this["PTPtr"] = {}; + } + if (!("PPT" in $$source)) { + this["PPT"] = {}; + } + if (!("PPTPtr" in $$source)) { + this["PPTPtr"] = {}; + } + if (!("PA" in $$source)) { + this["PA"] = {}; + } + if (!("PAPtr" in $$source)) { + this["PAPtr"] = {}; + } + if (!("PPA" in $$source)) { + this["PPA"] = {}; + } + if (!("PPAPtr" in $$source)) { + this["PPAPtr"] = {}; + } + if (!("IT" in $$source)) { + this["IT"] = {}; + } + if (!("ITPtr" in $$source)) { + this["ITPtr"] = {}; + } + if (!("IPT" in $$source)) { + this["IPT"] = {}; + } + if (!("IPTPtr" in $$source)) { + this["IPTPtr"] = {}; + } + if (!("IA" in $$source)) { + this["IA"] = {}; + } + if (!("IAPtr" in $$source)) { + this["IAPtr"] = {}; + } + if (!("IPA" in $$source)) { + this["IPA"] = {}; + } + if (!("IPAPtr" in $$source)) { + this["IPAPtr"] = {}; + } + if (!("TPR" in $$source)) { + this["TPR"] = {}; + } + if (!("TPRPtr" in $$source)) { + this["TPRPtr"] = {}; + } + if (!("TPS" in $$source)) { + this["TPS"] = {}; + } + if (!("TPSPtr" in $$source)) { + this["TPSPtr"] = {}; + } + if (!("TPT" in $$source)) { + this["TPT"] = {}; + } + if (!("TPTPtr" in $$source)) { + this["TPTPtr"] = {}; + } + if (!("TPU" in $$source)) { + this["TPU"] = {}; + } + if (!("TPUPtr" in $$source)) { + this["TPUPtr"] = {}; + } + if (!("TPV" in $$source)) { + this["TPV"] = {}; + } + if (!("TPVPtr" in $$source)) { + this["TPVPtr"] = {}; + } + if (!("TPW" in $$source)) { + this["TPW"] = {}; + } + if (!("TPWPtr" in $$source)) { + this["TPWPtr"] = {}; + } + if (!("TPX" in $$source)) { + this["TPX"] = {}; + } + if (!("TPXPtr" in $$source)) { + this["TPXPtr"] = {}; + } + if (!("TPY" in $$source)) { + this["TPY"] = {}; + } + if (!("TPYPtr" in $$source)) { + this["TPYPtr"] = {}; + } + if (!("TPZ" in $$source)) { + this["TPZ"] = {}; + } + if (!("TPZPtr" in $$source)) { + this["TPZPtr"] = {}; + } + if (!("GAR" in $$source)) { + this["GAR"] = {}; + } + if (!("GARPtr" in $$source)) { + this["GARPtr"] = {}; + } + if (!("GAS" in $$source)) { + this["GAS"] = {}; + } + if (!("GASPtr" in $$source)) { + this["GASPtr"] = {}; + } + if (!("GAT" in $$source)) { + this["GAT"] = {}; + } + if (!("GATPtr" in $$source)) { + this["GATPtr"] = {}; + } + if (!("GAU" in $$source)) { + this["GAU"] = {}; + } + if (!("GAUPtr" in $$source)) { + this["GAUPtr"] = {}; + } + if (!("GAV" in $$source)) { + this["GAV"] = {}; + } + if (!("GAVPtr" in $$source)) { + this["GAVPtr"] = {}; + } + if (!("GAW" in $$source)) { + this["GAW"] = {}; + } + if (!("GAWPtr" in $$source)) { + this["GAWPtr"] = {}; + } + if (!("GAX" in $$source)) { + this["GAX"] = {}; + } + if (!("GAXPtr" in $$source)) { + this["GAXPtr"] = {}; + } + if (!("GAY" in $$source)) { + this["GAY"] = {}; + } + if (!("GAYPtr" in $$source)) { + this["GAYPtr"] = {}; + } + if (!("GAZ" in $$source)) { + this["GAZ"] = {}; + } + if (!("GAZPtr" in $$source)) { + this["GAZPtr"] = {}; + } + if (!("GACi" in $$source)) { + this["GACi"] = {}; + } + if (!("GACV" in $$source)) { + this["GACV"] = {}; + } + if (!("GACP" in $$source)) { + this["GACP"] = {}; + } + if (!("GACiPtr" in $$source)) { + this["GACiPtr"] = {}; + } + if (!("GACVPtr" in $$source)) { + this["GACVPtr"] = {}; + } + if (!("GACPPtr" in $$source)) { + this["GACPPtr"] = {}; + } + if (!("GABi" in $$source)) { + this["GABi"] = {}; + } + if (!("GABs" in $$source)) { + this["GABs"] = {}; + } + if (!("GABiPtr" in $$source)) { + this["GABiPtr"] = {}; + } + if (!("GABT" in $$source)) { + this["GABT"] = {}; + } + if (!("GABTPtr" in $$source)) { + this["GABTPtr"] = {}; + } + if (!("GAGT" in $$source)) { + this["GAGT"] = {}; + } + if (!("GAGTPtr" in $$source)) { + this["GAGTPtr"] = {}; + } + if (!("GANBV" in $$source)) { + this["GANBV"] = {}; + } + if (!("GANBP" in $$source)) { + this["GANBP"] = {}; + } + if (!("GANBVPtr" in $$source)) { + this["GANBVPtr"] = {}; + } + if (!("GANBPPtr" in $$source)) { + this["GANBPPtr"] = {}; + } + if (!("GAPlV1" in $$source)) { + this["GAPlV1"] = {}; + } + if (!("GAPlV2" in $$source)) { + this["GAPlV2"] = {}; + } + if (!("GAPlP1" in $$source)) { + this["GAPlP1"] = {}; + } + if (!("GAPlP2" in $$source)) { + this["GAPlP2"] = {}; + } + if (!("GAPlVPtr" in $$source)) { + this["GAPlVPtr"] = {}; + } + if (!("GAPlPPtr" in $$source)) { + this["GAPlPPtr"] = {}; + } + if (!("GAMi" in $$source)) { + this["GAMi"] = {}; + } + if (!("GAMS" in $$source)) { + this["GAMS"] = {}; + } + if (!("GAMV" in $$source)) { + this["GAMV"] = {}; + } + if (!("GAMSPtr" in $$source)) { + this["GAMSPtr"] = {}; + } + if (!("GAMVPtr" in $$source)) { + this["GAMVPtr"] = {}; + } + if (!("GAII" in $$source)) { + this["GAII"] = {}; + } + if (!("GAIV" in $$source)) { + this["GAIV"] = {}; + } + if (!("GAIP" in $$source)) { + this["GAIP"] = {}; + } + if (!("GAIIPtr" in $$source)) { + this["GAIIPtr"] = {}; + } + if (!("GAIVPtr" in $$source)) { + this["GAIVPtr"] = {}; + } + if (!("GAIPPtr" in $$source)) { + this["GAIPPtr"] = {}; + } + if (!("GAPrV" in $$source)) { + this["GAPrV"] = {}; + } + if (!("GAPrP" in $$source)) { + this["GAPrP"] = {}; + } + if (!("GAPrVPtr" in $$source)) { + this["GAPrVPtr"] = {}; + } + if (!("GAPrPPtr" in $$source)) { + this["GAPrPPtr"] = {}; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class Maps. + */ + static createFrom($$createParamR: (source: any) => R, $$createParamS: (source: any) => S, $$createParamT: (source: any) => T, $$createParamU: (source: any) => U, $$createParamV: (source: any) => V, $$createParamW: (source: any) => W, $$createParamX: (source: any) => X, $$createParamY: (source: any) => Y, $$createParamZ: (source: any) => Z): ($$source?: any) => Maps { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField2_0 = $$createType2; + const $$createField3_0 = $$createType3; + const $$createField4_0 = $$createType4; + const $$createField5_0 = $$createType5; + const $$createField6_0 = $$createType6; + const $$createField7_0 = $$createType7; + const $$createField8_0 = $$createType8; + const $$createField9_0 = $$createType9; + const $$createField10_0 = $$createType10; + const $$createField11_0 = $$createType11; + const $$createField12_0 = $$createType12; + const $$createField13_0 = $$createType13; + const $$createField14_0 = $$createType14; + const $$createField15_0 = $$createType15; + const $$createField16_0 = $$createType16; + const $$createField17_0 = $$createType17; + const $$createField18_0 = $$createType18; + const $$createField19_0 = $$createType19; + const $$createField20_0 = $$createType20; + const $$createField21_0 = $$createType21; + const $$createField22_0 = $$createType22; + const $$createField23_0 = $$createType23; + const $$createField24_0 = $$createType24; + const $$createField25_0 = $$createType25; + const $$createField26_0 = $$createType26; + const $$createField27_0 = $$createType27; + const $$createField28_0 = $$createType28; + const $$createField29_0 = $$createType29; + const $$createField30_0 = $$createType30; + const $$createField31_0 = $$createType31; + const $$createField32_0 = $$createType32; + const $$createField33_0 = $$createType33; + const $$createField34_0 = $$createType34; + const $$createField35_0 = $$createType35; + const $$createField36_0 = $$createType36; + const $$createField37_0 = $$createType37; + const $$createField38_0 = $$createType38; + const $$createField39_0 = $$createType39; + const $$createField40_0 = $$createType40; + const $$createField41_0 = $$createType41; + const $$createField42_0 = $$createType0; + const $$createField43_0 = $$createType42; + const $$createField44_0 = $$createType7; + const $$createField45_0 = $$createType43; + const $$createField46_0 = $$createType1; + const $$createField47_0 = $$createType44; + const $$createField48_0 = $$createType45; + const $$createField49_0 = $$createType46; + const $$createField50_0 = $$createType47; + const $$createField51_0 = $$createType15; + const $$createField52_0 = $$createType16; + const $$createField53_0 = $$createType16; + const $$createField54_0 = $$createType48; + const $$createField55_0 = $$createType49; + const $$createField56_0 = $$createType50; + const $$createField57_0 = $$createType51; + const $$createField58_0 = $$createType52; + const $$createField59_0 = $$createType17; + const $$createField60_0 = $$createType18; + const $$createField61_0 = $$createType18; + const $$createField62_0 = $$createType53; + const $$createField63_0 = $$createType54; + const $$createField64_0 = $$createType55; + const $$createField65_0 = $$createType56; + const $$createField66_0 = $$createType57; + const $$createField67_0 = $$createType23; + const $$createField68_0 = $$createType24; + const $$createField69_0 = $$createType24; + const $$createField70_0 = $$createType58; + const $$createField71_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField72_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField73_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField74_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField75_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField76_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField77_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField78_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField79_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField80_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField81_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField82_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField83_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField84_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField85_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField86_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField87_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField88_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField89_0 = $$createType59($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField90_0 = $$createType60($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField91_0 = $$createType61($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField92_0 = $$createType62($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField93_0 = $$createType63($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField94_0 = $$createType64($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField95_0 = $$createType65($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField96_0 = $$createType66($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField97_0 = $$createType67($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField98_0 = $$createType68($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField99_0 = $$createType69($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField100_0 = $$createType70($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField101_0 = $$createType71($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField102_0 = $$createType72($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField103_0 = $$createType73($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField104_0 = $$createType74($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField105_0 = $$createType75($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField106_0 = $$createType76($$createParamR, $$createParamS, $$createParamT, $$createParamU, $$createParamV, $$createParamW, $$createParamX, $$createParamY, $$createParamZ); + const $$createField107_0 = $$createType1; + const $$createField108_0 = $$createType15; + const $$createField109_0 = $$createType17; + const $$createField110_0 = $$createType8; + const $$createField111_0 = $$createType16; + const $$createField112_0 = $$createType18; + const $$createField113_0 = $$createType1; + const $$createField114_0 = $$createType7; + const $$createField115_0 = $$createType8; + const $$createField116_0 = $$createType77; + const $$createField117_0 = $$createType78; + const $$createField118_0 = $$createType15; + const $$createField119_0 = $$createType16; + const $$createField120_0 = $$createType15; + const $$createField121_0 = $$createType18; + const $$createField122_0 = $$createType16; + const $$createField123_0 = $$createType53; + const $$createField124_0 = $$createType15; + const $$createField125_0 = $$createType16; + const $$createField126_0 = $$createType17; + const $$createField127_0 = $$createType18; + const $$createField128_0 = $$createType16; + const $$createField129_0 = $$createType18; + const $$createField130_0 = $$createType2; + const $$createField131_0 = $$createType42; + const $$createField132_0 = $$createType15; + const $$createField133_0 = $$createType79; + const $$createField134_0 = $$createType16; + const $$createField135_0 = $$createType23; + const $$createField136_0 = $$createType15; + const $$createField137_0 = $$createType18; + const $$createField138_0 = $$createType24; + const $$createField139_0 = $$createType16; + const $$createField140_0 = $$createType53; + const $$createField141_0 = $$createType16; + const $$createField142_0 = $$createType18; + const $$createField143_0 = $$createType48; + const $$createField144_0 = $$createType53; + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Bool" in $$parsedSource) { + $$parsedSource["Bool"] = $$createField0_0($$parsedSource["Bool"]); + } + if ("Int" in $$parsedSource) { + $$parsedSource["Int"] = $$createField1_0($$parsedSource["Int"]); + } + if ("Uint" in $$parsedSource) { + $$parsedSource["Uint"] = $$createField2_0($$parsedSource["Uint"]); + } + if ("Float" in $$parsedSource) { + $$parsedSource["Float"] = $$createField3_0($$parsedSource["Float"]); + } + if ("Complex" in $$parsedSource) { + $$parsedSource["Complex"] = $$createField4_0($$parsedSource["Complex"]); + } + if ("Byte" in $$parsedSource) { + $$parsedSource["Byte"] = $$createField5_0($$parsedSource["Byte"]); + } + if ("Rune" in $$parsedSource) { + $$parsedSource["Rune"] = $$createField6_0($$parsedSource["Rune"]); + } + if ("String" in $$parsedSource) { + $$parsedSource["String"] = $$createField7_0($$parsedSource["String"]); + } + if ("IntPtr" in $$parsedSource) { + $$parsedSource["IntPtr"] = $$createField8_0($$parsedSource["IntPtr"]); + } + if ("UintPtr" in $$parsedSource) { + $$parsedSource["UintPtr"] = $$createField9_0($$parsedSource["UintPtr"]); + } + if ("FloatPtr" in $$parsedSource) { + $$parsedSource["FloatPtr"] = $$createField10_0($$parsedSource["FloatPtr"]); + } + if ("ComplexPtr" in $$parsedSource) { + $$parsedSource["ComplexPtr"] = $$createField11_0($$parsedSource["ComplexPtr"]); + } + if ("StringPtr" in $$parsedSource) { + $$parsedSource["StringPtr"] = $$createField12_0($$parsedSource["StringPtr"]); + } + if ("NTM" in $$parsedSource) { + $$parsedSource["NTM"] = $$createField13_0($$parsedSource["NTM"]); + } + if ("NTMPtr" in $$parsedSource) { + $$parsedSource["NTMPtr"] = $$createField14_0($$parsedSource["NTMPtr"]); + } + if ("VTM" in $$parsedSource) { + $$parsedSource["VTM"] = $$createField15_0($$parsedSource["VTM"]); + } + if ("VTMPtr" in $$parsedSource) { + $$parsedSource["VTMPtr"] = $$createField16_0($$parsedSource["VTMPtr"]); + } + if ("PTM" in $$parsedSource) { + $$parsedSource["PTM"] = $$createField17_0($$parsedSource["PTM"]); + } + if ("PTMPtr" in $$parsedSource) { + $$parsedSource["PTMPtr"] = $$createField18_0($$parsedSource["PTMPtr"]); + } + if ("JTM" in $$parsedSource) { + $$parsedSource["JTM"] = $$createField19_0($$parsedSource["JTM"]); + } + if ("JTMPtr" in $$parsedSource) { + $$parsedSource["JTMPtr"] = $$createField20_0($$parsedSource["JTMPtr"]); + } + if ("A" in $$parsedSource) { + $$parsedSource["A"] = $$createField21_0($$parsedSource["A"]); + } + if ("APtr" in $$parsedSource) { + $$parsedSource["APtr"] = $$createField22_0($$parsedSource["APtr"]); + } + if ("TM" in $$parsedSource) { + $$parsedSource["TM"] = $$createField23_0($$parsedSource["TM"]); + } + if ("TMPtr" in $$parsedSource) { + $$parsedSource["TMPtr"] = $$createField24_0($$parsedSource["TMPtr"]); + } + if ("CI" in $$parsedSource) { + $$parsedSource["CI"] = $$createField25_0($$parsedSource["CI"]); + } + if ("CIPtr" in $$parsedSource) { + $$parsedSource["CIPtr"] = $$createField26_0($$parsedSource["CIPtr"]); + } + if ("EI" in $$parsedSource) { + $$parsedSource["EI"] = $$createField27_0($$parsedSource["EI"]); + } + if ("EIPtr" in $$parsedSource) { + $$parsedSource["EIPtr"] = $$createField28_0($$parsedSource["EIPtr"]); + } + if ("EV" in $$parsedSource) { + $$parsedSource["EV"] = $$createField29_0($$parsedSource["EV"]); + } + if ("EVPtr" in $$parsedSource) { + $$parsedSource["EVPtr"] = $$createField30_0($$parsedSource["EVPtr"]); + } + if ("EVP" in $$parsedSource) { + $$parsedSource["EVP"] = $$createField31_0($$parsedSource["EVP"]); + } + if ("EVPPtr" in $$parsedSource) { + $$parsedSource["EVPPtr"] = $$createField32_0($$parsedSource["EVPPtr"]); + } + if ("EP" in $$parsedSource) { + $$parsedSource["EP"] = $$createField33_0($$parsedSource["EP"]); + } + if ("EPPtr" in $$parsedSource) { + $$parsedSource["EPPtr"] = $$createField34_0($$parsedSource["EPPtr"]); + } + if ("EPP" in $$parsedSource) { + $$parsedSource["EPP"] = $$createField35_0($$parsedSource["EPP"]); + } + if ("EPPPtr" in $$parsedSource) { + $$parsedSource["EPPPtr"] = $$createField36_0($$parsedSource["EPPPtr"]); + } + if ("ECI" in $$parsedSource) { + $$parsedSource["ECI"] = $$createField37_0($$parsedSource["ECI"]); + } + if ("ECIPtr" in $$parsedSource) { + $$parsedSource["ECIPtr"] = $$createField38_0($$parsedSource["ECIPtr"]); + } + if ("EOI" in $$parsedSource) { + $$parsedSource["EOI"] = $$createField39_0($$parsedSource["EOI"]); + } + if ("EOIPtr" in $$parsedSource) { + $$parsedSource["EOIPtr"] = $$createField40_0($$parsedSource["EOIPtr"]); + } + if ("WT" in $$parsedSource) { + $$parsedSource["WT"] = $$createField41_0($$parsedSource["WT"]); + } + if ("WA" in $$parsedSource) { + $$parsedSource["WA"] = $$createField42_0($$parsedSource["WA"]); + } + if ("ST" in $$parsedSource) { + $$parsedSource["ST"] = $$createField43_0($$parsedSource["ST"]); + } + if ("SA" in $$parsedSource) { + $$parsedSource["SA"] = $$createField44_0($$parsedSource["SA"]); + } + if ("IntT" in $$parsedSource) { + $$parsedSource["IntT"] = $$createField45_0($$parsedSource["IntT"]); + } + if ("IntA" in $$parsedSource) { + $$parsedSource["IntA"] = $$createField46_0($$parsedSource["IntA"]); + } + if ("VT" in $$parsedSource) { + $$parsedSource["VT"] = $$createField47_0($$parsedSource["VT"]); + } + if ("VTPtr" in $$parsedSource) { + $$parsedSource["VTPtr"] = $$createField48_0($$parsedSource["VTPtr"]); + } + if ("VPT" in $$parsedSource) { + $$parsedSource["VPT"] = $$createField49_0($$parsedSource["VPT"]); + } + if ("VPTPtr" in $$parsedSource) { + $$parsedSource["VPTPtr"] = $$createField50_0($$parsedSource["VPTPtr"]); + } + if ("VA" in $$parsedSource) { + $$parsedSource["VA"] = $$createField51_0($$parsedSource["VA"]); + } + if ("VAPtr" in $$parsedSource) { + $$parsedSource["VAPtr"] = $$createField52_0($$parsedSource["VAPtr"]); + } + if ("VPA" in $$parsedSource) { + $$parsedSource["VPA"] = $$createField53_0($$parsedSource["VPA"]); + } + if ("VPAPtr" in $$parsedSource) { + $$parsedSource["VPAPtr"] = $$createField54_0($$parsedSource["VPAPtr"]); + } + if ("PT" in $$parsedSource) { + $$parsedSource["PT"] = $$createField55_0($$parsedSource["PT"]); + } + if ("PTPtr" in $$parsedSource) { + $$parsedSource["PTPtr"] = $$createField56_0($$parsedSource["PTPtr"]); + } + if ("PPT" in $$parsedSource) { + $$parsedSource["PPT"] = $$createField57_0($$parsedSource["PPT"]); + } + if ("PPTPtr" in $$parsedSource) { + $$parsedSource["PPTPtr"] = $$createField58_0($$parsedSource["PPTPtr"]); + } + if ("PA" in $$parsedSource) { + $$parsedSource["PA"] = $$createField59_0($$parsedSource["PA"]); + } + if ("PAPtr" in $$parsedSource) { + $$parsedSource["PAPtr"] = $$createField60_0($$parsedSource["PAPtr"]); + } + if ("PPA" in $$parsedSource) { + $$parsedSource["PPA"] = $$createField61_0($$parsedSource["PPA"]); + } + if ("PPAPtr" in $$parsedSource) { + $$parsedSource["PPAPtr"] = $$createField62_0($$parsedSource["PPAPtr"]); + } + if ("IT" in $$parsedSource) { + $$parsedSource["IT"] = $$createField63_0($$parsedSource["IT"]); + } + if ("ITPtr" in $$parsedSource) { + $$parsedSource["ITPtr"] = $$createField64_0($$parsedSource["ITPtr"]); + } + if ("IPT" in $$parsedSource) { + $$parsedSource["IPT"] = $$createField65_0($$parsedSource["IPT"]); + } + if ("IPTPtr" in $$parsedSource) { + $$parsedSource["IPTPtr"] = $$createField66_0($$parsedSource["IPTPtr"]); + } + if ("IA" in $$parsedSource) { + $$parsedSource["IA"] = $$createField67_0($$parsedSource["IA"]); + } + if ("IAPtr" in $$parsedSource) { + $$parsedSource["IAPtr"] = $$createField68_0($$parsedSource["IAPtr"]); + } + if ("IPA" in $$parsedSource) { + $$parsedSource["IPA"] = $$createField69_0($$parsedSource["IPA"]); + } + if ("IPAPtr" in $$parsedSource) { + $$parsedSource["IPAPtr"] = $$createField70_0($$parsedSource["IPAPtr"]); + } + if ("TPR" in $$parsedSource) { + $$parsedSource["TPR"] = $$createField71_0($$parsedSource["TPR"]); + } + if ("TPRPtr" in $$parsedSource) { + $$parsedSource["TPRPtr"] = $$createField72_0($$parsedSource["TPRPtr"]); + } + if ("TPS" in $$parsedSource) { + $$parsedSource["TPS"] = $$createField73_0($$parsedSource["TPS"]); + } + if ("TPSPtr" in $$parsedSource) { + $$parsedSource["TPSPtr"] = $$createField74_0($$parsedSource["TPSPtr"]); + } + if ("TPT" in $$parsedSource) { + $$parsedSource["TPT"] = $$createField75_0($$parsedSource["TPT"]); + } + if ("TPTPtr" in $$parsedSource) { + $$parsedSource["TPTPtr"] = $$createField76_0($$parsedSource["TPTPtr"]); + } + if ("TPU" in $$parsedSource) { + $$parsedSource["TPU"] = $$createField77_0($$parsedSource["TPU"]); + } + if ("TPUPtr" in $$parsedSource) { + $$parsedSource["TPUPtr"] = $$createField78_0($$parsedSource["TPUPtr"]); + } + if ("TPV" in $$parsedSource) { + $$parsedSource["TPV"] = $$createField79_0($$parsedSource["TPV"]); + } + if ("TPVPtr" in $$parsedSource) { + $$parsedSource["TPVPtr"] = $$createField80_0($$parsedSource["TPVPtr"]); + } + if ("TPW" in $$parsedSource) { + $$parsedSource["TPW"] = $$createField81_0($$parsedSource["TPW"]); + } + if ("TPWPtr" in $$parsedSource) { + $$parsedSource["TPWPtr"] = $$createField82_0($$parsedSource["TPWPtr"]); + } + if ("TPX" in $$parsedSource) { + $$parsedSource["TPX"] = $$createField83_0($$parsedSource["TPX"]); + } + if ("TPXPtr" in $$parsedSource) { + $$parsedSource["TPXPtr"] = $$createField84_0($$parsedSource["TPXPtr"]); + } + if ("TPY" in $$parsedSource) { + $$parsedSource["TPY"] = $$createField85_0($$parsedSource["TPY"]); + } + if ("TPYPtr" in $$parsedSource) { + $$parsedSource["TPYPtr"] = $$createField86_0($$parsedSource["TPYPtr"]); + } + if ("TPZ" in $$parsedSource) { + $$parsedSource["TPZ"] = $$createField87_0($$parsedSource["TPZ"]); + } + if ("TPZPtr" in $$parsedSource) { + $$parsedSource["TPZPtr"] = $$createField88_0($$parsedSource["TPZPtr"]); + } + if ("GAR" in $$parsedSource) { + $$parsedSource["GAR"] = $$createField89_0($$parsedSource["GAR"]); + } + if ("GARPtr" in $$parsedSource) { + $$parsedSource["GARPtr"] = $$createField90_0($$parsedSource["GARPtr"]); + } + if ("GAS" in $$parsedSource) { + $$parsedSource["GAS"] = $$createField91_0($$parsedSource["GAS"]); + } + if ("GASPtr" in $$parsedSource) { + $$parsedSource["GASPtr"] = $$createField92_0($$parsedSource["GASPtr"]); + } + if ("GAT" in $$parsedSource) { + $$parsedSource["GAT"] = $$createField93_0($$parsedSource["GAT"]); + } + if ("GATPtr" in $$parsedSource) { + $$parsedSource["GATPtr"] = $$createField94_0($$parsedSource["GATPtr"]); + } + if ("GAU" in $$parsedSource) { + $$parsedSource["GAU"] = $$createField95_0($$parsedSource["GAU"]); + } + if ("GAUPtr" in $$parsedSource) { + $$parsedSource["GAUPtr"] = $$createField96_0($$parsedSource["GAUPtr"]); + } + if ("GAV" in $$parsedSource) { + $$parsedSource["GAV"] = $$createField97_0($$parsedSource["GAV"]); + } + if ("GAVPtr" in $$parsedSource) { + $$parsedSource["GAVPtr"] = $$createField98_0($$parsedSource["GAVPtr"]); + } + if ("GAW" in $$parsedSource) { + $$parsedSource["GAW"] = $$createField99_0($$parsedSource["GAW"]); + } + if ("GAWPtr" in $$parsedSource) { + $$parsedSource["GAWPtr"] = $$createField100_0($$parsedSource["GAWPtr"]); + } + if ("GAX" in $$parsedSource) { + $$parsedSource["GAX"] = $$createField101_0($$parsedSource["GAX"]); + } + if ("GAXPtr" in $$parsedSource) { + $$parsedSource["GAXPtr"] = $$createField102_0($$parsedSource["GAXPtr"]); + } + if ("GAY" in $$parsedSource) { + $$parsedSource["GAY"] = $$createField103_0($$parsedSource["GAY"]); + } + if ("GAYPtr" in $$parsedSource) { + $$parsedSource["GAYPtr"] = $$createField104_0($$parsedSource["GAYPtr"]); + } + if ("GAZ" in $$parsedSource) { + $$parsedSource["GAZ"] = $$createField105_0($$parsedSource["GAZ"]); + } + if ("GAZPtr" in $$parsedSource) { + $$parsedSource["GAZPtr"] = $$createField106_0($$parsedSource["GAZPtr"]); + } + if ("GACi" in $$parsedSource) { + $$parsedSource["GACi"] = $$createField107_0($$parsedSource["GACi"]); + } + if ("GACV" in $$parsedSource) { + $$parsedSource["GACV"] = $$createField108_0($$parsedSource["GACV"]); + } + if ("GACP" in $$parsedSource) { + $$parsedSource["GACP"] = $$createField109_0($$parsedSource["GACP"]); + } + if ("GACiPtr" in $$parsedSource) { + $$parsedSource["GACiPtr"] = $$createField110_0($$parsedSource["GACiPtr"]); + } + if ("GACVPtr" in $$parsedSource) { + $$parsedSource["GACVPtr"] = $$createField111_0($$parsedSource["GACVPtr"]); + } + if ("GACPPtr" in $$parsedSource) { + $$parsedSource["GACPPtr"] = $$createField112_0($$parsedSource["GACPPtr"]); + } + if ("GABi" in $$parsedSource) { + $$parsedSource["GABi"] = $$createField113_0($$parsedSource["GABi"]); + } + if ("GABs" in $$parsedSource) { + $$parsedSource["GABs"] = $$createField114_0($$parsedSource["GABs"]); + } + if ("GABiPtr" in $$parsedSource) { + $$parsedSource["GABiPtr"] = $$createField115_0($$parsedSource["GABiPtr"]); + } + if ("GABT" in $$parsedSource) { + $$parsedSource["GABT"] = $$createField116_0($$parsedSource["GABT"]); + } + if ("GABTPtr" in $$parsedSource) { + $$parsedSource["GABTPtr"] = $$createField117_0($$parsedSource["GABTPtr"]); + } + if ("GAGT" in $$parsedSource) { + $$parsedSource["GAGT"] = $$createField118_0($$parsedSource["GAGT"]); + } + if ("GAGTPtr" in $$parsedSource) { + $$parsedSource["GAGTPtr"] = $$createField119_0($$parsedSource["GAGTPtr"]); + } + if ("GANBV" in $$parsedSource) { + $$parsedSource["GANBV"] = $$createField120_0($$parsedSource["GANBV"]); + } + if ("GANBP" in $$parsedSource) { + $$parsedSource["GANBP"] = $$createField121_0($$parsedSource["GANBP"]); + } + if ("GANBVPtr" in $$parsedSource) { + $$parsedSource["GANBVPtr"] = $$createField122_0($$parsedSource["GANBVPtr"]); + } + if ("GANBPPtr" in $$parsedSource) { + $$parsedSource["GANBPPtr"] = $$createField123_0($$parsedSource["GANBPPtr"]); + } + if ("GAPlV1" in $$parsedSource) { + $$parsedSource["GAPlV1"] = $$createField124_0($$parsedSource["GAPlV1"]); + } + if ("GAPlV2" in $$parsedSource) { + $$parsedSource["GAPlV2"] = $$createField125_0($$parsedSource["GAPlV2"]); + } + if ("GAPlP1" in $$parsedSource) { + $$parsedSource["GAPlP1"] = $$createField126_0($$parsedSource["GAPlP1"]); + } + if ("GAPlP2" in $$parsedSource) { + $$parsedSource["GAPlP2"] = $$createField127_0($$parsedSource["GAPlP2"]); + } + if ("GAPlVPtr" in $$parsedSource) { + $$parsedSource["GAPlVPtr"] = $$createField128_0($$parsedSource["GAPlVPtr"]); + } + if ("GAPlPPtr" in $$parsedSource) { + $$parsedSource["GAPlPPtr"] = $$createField129_0($$parsedSource["GAPlPPtr"]); + } + if ("GAMi" in $$parsedSource) { + $$parsedSource["GAMi"] = $$createField130_0($$parsedSource["GAMi"]); + } + if ("GAMS" in $$parsedSource) { + $$parsedSource["GAMS"] = $$createField131_0($$parsedSource["GAMS"]); + } + if ("GAMV" in $$parsedSource) { + $$parsedSource["GAMV"] = $$createField132_0($$parsedSource["GAMV"]); + } + if ("GAMSPtr" in $$parsedSource) { + $$parsedSource["GAMSPtr"] = $$createField133_0($$parsedSource["GAMSPtr"]); + } + if ("GAMVPtr" in $$parsedSource) { + $$parsedSource["GAMVPtr"] = $$createField134_0($$parsedSource["GAMVPtr"]); + } + if ("GAII" in $$parsedSource) { + $$parsedSource["GAII"] = $$createField135_0($$parsedSource["GAII"]); + } + if ("GAIV" in $$parsedSource) { + $$parsedSource["GAIV"] = $$createField136_0($$parsedSource["GAIV"]); + } + if ("GAIP" in $$parsedSource) { + $$parsedSource["GAIP"] = $$createField137_0($$parsedSource["GAIP"]); + } + if ("GAIIPtr" in $$parsedSource) { + $$parsedSource["GAIIPtr"] = $$createField138_0($$parsedSource["GAIIPtr"]); + } + if ("GAIVPtr" in $$parsedSource) { + $$parsedSource["GAIVPtr"] = $$createField139_0($$parsedSource["GAIVPtr"]); + } + if ("GAIPPtr" in $$parsedSource) { + $$parsedSource["GAIPPtr"] = $$createField140_0($$parsedSource["GAIPPtr"]); + } + if ("GAPrV" in $$parsedSource) { + $$parsedSource["GAPrV"] = $$createField141_0($$parsedSource["GAPrV"]); + } + if ("GAPrP" in $$parsedSource) { + $$parsedSource["GAPrP"] = $$createField142_0($$parsedSource["GAPrP"]); + } + if ("GAPrVPtr" in $$parsedSource) { + $$parsedSource["GAPrVPtr"] = $$createField143_0($$parsedSource["GAPrVPtr"]); + } + if ("GAPrPPtr" in $$parsedSource) { + $$parsedSource["GAPrPPtr"] = $$createField144_0($$parsedSource["GAPrPPtr"]); + } + return new Maps($$parsedSource as Partial>); + }; + } +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = $Create.Map($Create.Any, $Create.Any); +const $$createType1 = $Create.Map($Create.Any, $Create.Any); +const $$createType2 = $Create.Map($Create.Any, $Create.Any); +const $$createType3 = $Create.Map($Create.Any, $Create.Any); +const $$createType4 = $Create.Map($Create.Any, $Create.Any); +const $$createType5 = $Create.Map($Create.Any, $Create.Any); +const $$createType6 = $Create.Map($Create.Any, $Create.Any); +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = $Create.Map($Create.Any, $Create.Any); +const $$createType9 = $Create.Map($Create.Any, $Create.Any); +const $$createType10 = $Create.Map($Create.Any, $Create.Any); +const $$createType11 = $Create.Map($Create.Any, $Create.Any); +const $$createType12 = $Create.Map($Create.Any, $Create.Any); +const $$createType13 = $Create.Map($Create.Any, $Create.Any); +const $$createType14 = $Create.Map($Create.Any, $Create.Any); +const $$createType15 = $Create.Map($Create.Any, $Create.Any); +const $$createType16 = $Create.Map($Create.Any, $Create.Any); +const $$createType17 = $Create.Map($Create.Any, $Create.Any); +const $$createType18 = $Create.Map($Create.Any, $Create.Any); +const $$createType19 = $Create.Map($Create.Any, $Create.Any); +const $$createType20 = $Create.Map($Create.Any, $Create.Any); +const $$createType21 = $Create.Map($Create.Any, $Create.Any); +const $$createType22 = $Create.Map($Create.Any, $Create.Any); +const $$createType23 = $Create.Map($Create.Any, $Create.Any); +const $$createType24 = $Create.Map($Create.Any, $Create.Any); +const $$createType25 = $Create.Map($Create.Any, $Create.Any); +const $$createType26 = $Create.Map($Create.Any, $Create.Any); +const $$createType27 = $Create.Map($Create.Any, $Create.Any); +const $$createType28 = $Create.Map($Create.Any, $Create.Any); +const $$createType29 = $Create.Map($Create.Any, $Create.Any); +const $$createType30 = $Create.Map($Create.Any, $Create.Any); +const $$createType31 = $Create.Map($Create.Any, $Create.Any); +const $$createType32 = $Create.Map($Create.Any, $Create.Any); +const $$createType33 = $Create.Map($Create.Any, $Create.Any); +const $$createType34 = $Create.Map($Create.Any, $Create.Any); +const $$createType35 = $Create.Map($Create.Any, $Create.Any); +const $$createType36 = $Create.Map($Create.Any, $Create.Any); +const $$createType37 = $Create.Map($Create.Any, $Create.Any); +const $$createType38 = $Create.Map($Create.Any, $Create.Any); +const $$createType39 = $Create.Map($Create.Any, $Create.Any); +const $$createType40 = $Create.Map($Create.Any, $Create.Any); +const $$createType41 = $Create.Map($Create.Any, $Create.Any); +const $$createType42 = $Create.Map($Create.Any, $Create.Any); +const $$createType43 = $Create.Map($Create.Any, $Create.Any); +const $$createType44 = $Create.Map($Create.Any, $Create.Any); +const $$createType45 = $Create.Map($Create.Any, $Create.Any); +const $$createType46 = $Create.Map($Create.Any, $Create.Any); +const $$createType47 = $Create.Map($Create.Any, $Create.Any); +const $$createType48 = $Create.Map($Create.Any, $Create.Any); +const $$createType49 = $Create.Map($Create.Any, $Create.Any); +const $$createType50 = $Create.Map($Create.Any, $Create.Any); +const $$createType51 = $Create.Map($Create.Any, $Create.Any); +const $$createType52 = $Create.Map($Create.Any, $Create.Any); +const $$createType53 = $Create.Map($Create.Any, $Create.Any); +const $$createType54 = $Create.Map($Create.Any, $Create.Any); +const $$createType55 = $Create.Map($Create.Any, $Create.Any); +const $$createType56 = $Create.Map($Create.Any, $Create.Any); +const $$createType57 = $Create.Map($Create.Any, $Create.Any); +const $$createType58 = $Create.Map($Create.Any, $Create.Any); +const $$createType59 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType60 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType61 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType62 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType63 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType64 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType65 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType66 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType67 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType68 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType69 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType70 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType71 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType72 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType73 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType74 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType75 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType76 = ($$createParamR: any, $$createParamS: any, $$createParamT: any, $$createParamU: any, $$createParamV: any, $$createParamW: any, $$createParamX: any, $$createParamY: any, $$createParamZ: any) => $Create.Map($Create.Any, $Create.Any); +const $$createType77 = $Create.Map($Create.Any, $Create.Any); +const $$createType78 = $Create.Map($Create.Any, $Create.Any); +const $$createType79 = $Create.Map($Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..d62acda96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByName("main.Service.Method").then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Maps.createFrom($Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any, $Create.Any); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..bbe49e32f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,36 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export { + Data, + ImplicitNonMarshaler, + NonMarshaler +} from "./models.js"; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..d8db23862 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,545 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export class Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; + + /** Creates a new Data instance. */ + constructor($$source: Partial = {}) { + if (!("NM" in $$source)) { + this["NM"] = (new NonMarshaler()); + } + if (!("NMPtr" in $$source)) { + this["NMPtr"] = null; + } + if (!("VJM" in $$source)) { + this["VJM"] = null; + } + if (!("VJMPtr" in $$source)) { + this["VJMPtr"] = null; + } + if (!("PJM" in $$source)) { + this["PJM"] = null; + } + if (!("PJMPtr" in $$source)) { + this["PJMPtr"] = null; + } + if (!("VTM" in $$source)) { + this["VTM"] = ""; + } + if (!("VTMPtr" in $$source)) { + this["VTMPtr"] = null; + } + if (!("PTM" in $$source)) { + this["PTM"] = ""; + } + if (!("PTMPtr" in $$source)) { + this["PTMPtr"] = null; + } + if (!("VM" in $$source)) { + this["VM"] = null; + } + if (!("VMPtr" in $$source)) { + this["VMPtr"] = null; + } + if (!("PM" in $$source)) { + this["PM"] = null; + } + if (!("PMPtr" in $$source)) { + this["PMPtr"] = null; + } + if (!("UJM" in $$source)) { + this["UJM"] = null; + } + if (!("UJMPtr" in $$source)) { + this["UJMPtr"] = null; + } + if (!("UTM" in $$source)) { + this["UTM"] = ""; + } + if (!("UTMPtr" in $$source)) { + this["UTMPtr"] = null; + } + if (!("UM" in $$source)) { + this["UM"] = null; + } + if (!("UMPtr" in $$source)) { + this["UMPtr"] = null; + } + if (!("JM" in $$source)) { + this["JM"] = null; + } + if (!("JMPtr" in $$source)) { + this["JMPtr"] = null; + } + if (!("TM" in $$source)) { + this["TM"] = ""; + } + if (!("TMPtr" in $$source)) { + this["TMPtr"] = null; + } + if (!("CJM" in $$source)) { + this["CJM"] = null; + } + if (!("CJMPtr" in $$source)) { + this["CJMPtr"] = null; + } + if (!("CTM" in $$source)) { + this["CTM"] = ""; + } + if (!("CTMPtr" in $$source)) { + this["CTMPtr"] = null; + } + if (!("CM" in $$source)) { + this["CM"] = null; + } + if (!("CMPtr" in $$source)) { + this["CMPtr"] = null; + } + if (!("ANM" in $$source)) { + this["ANM"] = {}; + } + if (!("ANMPtr" in $$source)) { + this["ANMPtr"] = null; + } + if (!("AJM" in $$source)) { + this["AJM"] = null; + } + if (!("AJMPtr" in $$source)) { + this["AJMPtr"] = null; + } + if (!("ATM" in $$source)) { + this["ATM"] = ""; + } + if (!("ATMPtr" in $$source)) { + this["ATMPtr"] = null; + } + if (!("AM" in $$source)) { + this["AM"] = null; + } + if (!("AMPtr" in $$source)) { + this["AMPtr"] = null; + } + if (!("ImJM" in $$source)) { + this["ImJM"] = null; + } + if (!("ImJMPtr" in $$source)) { + this["ImJMPtr"] = null; + } + if (!("ImTM" in $$source)) { + this["ImTM"] = ""; + } + if (!("ImTMPtr" in $$source)) { + this["ImTMPtr"] = null; + } + if (!("ImM" in $$source)) { + this["ImM"] = null; + } + if (!("ImMPtr" in $$source)) { + this["ImMPtr"] = null; + } + if (!("ImNJ" in $$source)) { + this["ImNJ"] = ""; + } + if (!("ImNJPtr" in $$source)) { + this["ImNJPtr"] = null; + } + if (!("ImNT" in $$source)) { + this["ImNT"] = null; + } + if (!("ImNTPtr" in $$source)) { + this["ImNTPtr"] = null; + } + if (!("ImNM" in $$source)) { + this["ImNM"] = (new ImplicitNonMarshaler()); + } + if (!("ImNMPtr" in $$source)) { + this["ImNMPtr"] = null; + } + if (!("ImJbT" in $$source)) { + this["ImJbT"] = null; + } + if (!("ImJbTPtr" in $$source)) { + this["ImJbTPtr"] = null; + } + if (!("ImTbJ" in $$source)) { + this["ImTbJ"] = null; + } + if (!("ImTbJPtr" in $$source)) { + this["ImTbJPtr"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Data instance from a string or object. + */ + static createFrom($$source: any = {}): Data { + const $$createField0_0 = $$createType0; + const $$createField1_0 = $$createType1; + const $$createField48_0 = $$createType2; + const $$createField49_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("NM" in $$parsedSource) { + $$parsedSource["NM"] = $$createField0_0($$parsedSource["NM"]); + } + if ("NMPtr" in $$parsedSource) { + $$parsedSource["NMPtr"] = $$createField1_0($$parsedSource["NMPtr"]); + } + if ("ImNM" in $$parsedSource) { + $$parsedSource["ImNM"] = $$createField48_0($$parsedSource["ImNM"]); + } + if ("ImNMPtr" in $$parsedSource) { + $$parsedSource["ImNMPtr"] = $$createField49_0($$parsedSource["ImNMPtr"]); + } + return new Data($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export class ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; + + /** Creates a new ImplicitNonMarshaler instance. */ + constructor($$source: Partial = {}) { + if (!("Marshaler" in $$source)) { + this["Marshaler"] = null; + } + if (!("TextMarshaler" in $$source)) { + this["TextMarshaler"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new ImplicitNonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): ImplicitNonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ImplicitNonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export class NonMarshaler { + + /** Creates a new NonMarshaler instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new NonMarshaler instance from a string or object. + */ + static createFrom($$source: any = {}): NonMarshaler { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new NonMarshaler($$parsedSource as Partial); + } +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; + +// Private type creation functions +const $$createType0 = NonMarshaler.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ImplicitNonMarshaler.createFrom; +const $$createType3 = $Create.Nullable($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..8e2af391b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByName("main.Service.Method").then(($result: any) => { + return $$createType0($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Data.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..19d990dbf --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..4cb328e1e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,163 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export class HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": { [_: string]: How }[]; + + /** Creates a new HowDifferent instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class HowDifferent. + */ + static createFrom($$createParamHow: (source: any) => How): ($$source?: any) => HowDifferent { + const $$createField1_0 = $$createType1($$createParamHow); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new HowDifferent($$parsedSource as Partial>); + }; + } +} + +/** + * Impersonator gets their fields from other people. + */ +export const Impersonator = other$0.OtherPerson; + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export class Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField1_0($$parsedSource["Friends"]); + } + return new Person($$parsedSource as Partial); + } +} + +export class personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; + + /** Creates a new personImpl instance. */ + constructor($$source: Partial = {}) { + if (!("Nickname" in $$source)) { + this["Nickname"] = ""; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Friends" in $$source)) { + this["Friends"] = Array.from({ length: 4 }, () => (new Impersonator())); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new personImpl instance from a string or object. + */ + static createFrom($$source: any = {}): personImpl { + const $$createField2_0 = $$createType3; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Friends" in $$parsedSource) { + $$parsedSource["Friends"] = $$createField2_0($$parsedSource["Friends"]); + } + return new personImpl($$parsedSource as Partial); + } +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export const PrivatePerson = personImpl; + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +// Private type creation functions +const $$createType0 = ($$createParamHow: any) => $Create.Map($Create.Any, $$createParamHow); +const $$createType1 = ($$createParamHow: any) => $Create.Array($$createType0($$createParamHow)); +const $$createType2 = other$0.OtherPerson.createFrom($Create.Any); +const $$createType3 = $Create.Array($$createType2); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..d2d973b28 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..711735a2b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,52 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +/** + * OtherPerson is like a person, but different. + */ +export class OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[]; + + /** Creates a new OtherPerson instance. */ + constructor($$source: Partial> = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Differences" in $$source)) { + this["Differences"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Given creation functions for each type parameter, + * returns a creation function for a concrete instance + * of the generic class OtherPerson. + */ + static createFrom($$createParamT: (source: any) => T): ($$source?: any) => OtherPerson { + const $$createField1_0 = $$createType0($$createParamT); + return ($$source: any = {}) => { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Differences" in $$parsedSource) { + $$parsedSource["Differences"] = $$createField1_0($$parsedSource["Differences"]); + } + return new OtherPerson($$parsedSource as Partial>); + }; + } +} + +// Private type creation functions +const $$createType0 = ($$createParamT: any) => $Create.Array($$createParamT); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..2e4c173df --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..fb5f5ce21 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $models.HowDifferent.createFrom($Create.Any); +const $$createType2 = $models.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..c82f44866 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..e3b2fe679 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByName("main.EmbedService.LikeThisOne").then(($result: any) => { + $result[0] = $$createType0($result[0]); + $result[1] = $$createType1($result[1]); + $result[2] = $$createType2($result[2]); + return $result; + }); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} + +// Private type creation functions +const $$createType0 = nobindingshere$0.Person.createFrom; +const $$createType1 = nobindingshere$0.HowDifferent.createFrom($Create.Any); +const $$createType2 = nobindingshere$0.personImpl.createFrom; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..192340e8e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..e437314f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..460b2b374 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..e437314f4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..d76f68d9d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..fc2efb6c1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..ea2dcf8a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..ea2dcf8a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,209 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] }): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] }): $CancellablePromise<{ [_: `${number}`]: number[] }> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in).then(($result: any) => { + return $$createType1($result); + }); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputNamedOutputs($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringArrayOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in).then(($result: any) => { + return $$createType2($result); + }); +} + +export function StringArrayInputStringOut($in: string[]): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in).then(($result: any) => { + return $$createType3($result); + }); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in).then(($result: any) => { + return $$createType4($result); + }); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} + +// Private type creation functions +const $$createType0 = $Create.Array($Create.Any); +const $$createType1 = $Create.Map($Create.Any, $$createType0); +const $$createType2 = $Create.Array($Create.Any); +const $$createType3 = $models.Person.createFrom; +const $$createType4 = $Create.Nullable($$createType3); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..cd282c90c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,43 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Parent" in $$source)) { + this["Parent"] = null; + } + if (!("Details" in $$source)) { + this["Details"] = {"Age": 0, "Address": {"Street": ""}}; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Parent" in $$parsedSource) { + $$parsedSource["Parent"] = $$createField1_0($$parsedSource["Parent"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..6a6a881dc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..1a2ffb266 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name).then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Person.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..f95c1b558 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f6eee9de8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export class Person { + "Name": string; + "Address": services$0.Address | null; + + /** Creates a new Person instance. */ + constructor($$source: Partial = {}) { + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Address" in $$source)) { + this["Address"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Person instance from a string or object. + */ + static createFrom($$source: any = {}): Person { + const $$createField1_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Address" in $$parsedSource) { + $$parsedSource["Address"] = $$createField1_0($$parsedSource["Address"]); + } + return new Person($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = services$0.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..e4d86b717 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..a4be6e904 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,35 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "/wails/runtime.js"; + +export class Address { + "Street": string; + "State": string; + "Country": string; + + /** Creates a new Address instance. */ + constructor($$source: Partial
= {}) { + if (!("Street" in $$source)) { + this["Street"] = ""; + } + if (!("State" in $$source)) { + this["State"] = ""; + } + if (!("Country" in $$source)) { + this["Country"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Address instance from a string or object. + */ + static createFrom($$source: any = {}): Address { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Address($$parsedSource as Partial
); + } +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..554897ea4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay").then(($result: any) => { + return $$createType1($result); + }); +} + +// Private type creation functions +const $$createType0 = $models.Address.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts new file mode 100644 index 000000000..8e7f0b3f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/json/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts new file mode 100644 index 000000000..51089b14d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/encoding/models.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..c371520b0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByID(1928502664, aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByID(1896499664, p); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByID(2240931744); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByID(643456960); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByID(914093800); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByID(1411160069, $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..75cbdc737 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,26 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + AliasGroup, + AliasStruct, + AliasedPerson, + EmptyAliasStruct, + EmptyStruct, + GenericAlias, + GenericMapAlias, + GenericPerson, + GenericPersonAlias, + GenericPtrAlias, + IndirectPersonAlias, + OtherAliasStruct, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..26b204c1f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,125 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export interface AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[] | null; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export interface EmptyStruct { +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U } | null; + +/** + * A generic struct containing an alias. + */ +export interface GenericPerson { + "Name": T; + "AliasedField": Alias; +} + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[] | null>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[] | null; +} + +/** + * A non-generic struct containing an alias. + */ +export interface Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; +} + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..5cba569c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByID(2241101727); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..dc425cfd5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByID(1556848345); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..a5334fce3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByID(1411160069, person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..f645e5730 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +export type { + Embedded1, + Embedded3, + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..7cdb88164 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,121 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export interface Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[] | null; + + /** + * Names has a + * multiline comment + */ + "Names": string[] | null; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[] | null; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export interface embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..99ffd566f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,24 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[] | null, $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null } | null, $4: (number | null)[] | null, ...other: string[]): $CancellablePromise<[$models.Person, any, number[] | null]> { + return $Call.ByID(1411160069, str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..6691fb86c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + */ +export interface Person { + "Name": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..e6a04adf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..bbf592890 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..256987ac8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,21 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface StructA { + "B": structB | null; +} + +export interface StructC { + "D": structD; +} + +export interface StructE { +} + +export interface structB { + "A": StructA | null; +} + +export interface structD { + "E": StructE; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..965a057ca --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByID(440020721); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..5395305fc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type Alias = Cyclic | null; + +export type Cyclic = ({ [_: string]: Alias } | null)[] | null; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[] | null}[] | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..bb2426022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello TS!"); +console.log("Hello TS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..9d2a673d6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByID(538079117, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..efe76e58b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + */ +export interface InternalModel { + "Field": string; +} + +/** + * An unexported model. + */ +export interface unexportedModel { + "Field": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..82d89784a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..71070d754 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,5 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Dummy { +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..e60cbea0c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByID(3518775569, $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByID(474018228, $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..57d7f73be --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByID(37626172, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..5a3127774 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByID(841558284); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByID(891891141); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByID(875113522); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByID(791225427); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByID(774447808); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..eda1dd8d0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..e66941c16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..74d673e16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export interface Person { + "Title": Title; + "Name": string; + "Age": Age; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..ade2383a0 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByID(1411160069, name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..887aee9ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..99b989f07 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..4cb206cc6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2007737399); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..70b85519b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export interface Person { + "Name": string; + "Address": other$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..8879fcfa2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(2447353446); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..f9b8d87e2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByID(1310150960, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..e3aeb8a64 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + Maps, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..aaabd3502 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,767 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export interface Maps { + /** + * Reject + */ + "Bool": { [_: string]: number } | null; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "Float": { [_: string]: number } | null; + + /** + * Reject + */ + "Complex": { [_: string]: number } | null; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "String": { [_: string]: number } | null; + + /** + * Reject + */ + "IntPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "UintPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "StringPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "NTM": { [_: string]: number } | null; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number } | null; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number } | null; + + /** + * Reject + */ + "PTM": { [_: string]: number } | null; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number } | null; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "A": { [_: string]: number } | null; + + /** + * Reject + */ + "APtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TM": { [_: string]: number } | null; + + /** + * Reject + */ + "TMPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "CI": { [_: string]: number } | null; + + /** + * Reject + */ + "CIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "EI": { [_: string]: number } | null; + + /** + * Reject + */ + "EIPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Reject + */ + "EP": { [_: string]: number } | null; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number } | null; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Reject + */ + "WT": { [_: string]: number } | null; + + /** + * Reject + */ + "WA": { [_: string]: number } | null; + + /** + * Accept + */ + "ST": { [_: StringType]: number } | null; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number } | null; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "VT": { [_: string]: number } | null; + + /** + * Reject + */ + "VTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "VPT": { [_: string]: number } | null; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number } | null; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number } | null; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number } | null; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PT": { [_: string]: number } | null; + + /** + * Reject + */ + "PTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PPT": { [_: string]: number } | null; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PA": { [_: string]: number } | null; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number } | null; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number } | null; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IT": { [_: string]: number } | null; + + /** + * Reject + */ + "ITPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPT": { [_: string]: number } | null; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IA": { [_: string]: number } | null; + + /** + * Reject + */ + "IAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPA": { [_: string]: number } | null; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number } | null; + + /** + * Reject + */ + "GACP": { [_: string]: number } | null; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number } | null; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GABT": { [_: string]: number } | null; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number } | null; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number } | null; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number } | null; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number } | null; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number } | null; +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..50d2f6d72 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..7c55b767b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + Data, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonMarshaler, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + NonMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..02d639994 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,309 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export interface Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export interface ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export interface NonMarshaler { +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..b175ebe96 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByID(4021345184); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..bf611e486 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export type { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..c42cce373 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,64 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export interface HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": ({ [_: string]: How } | null)[] | null; +} + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export interface Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +export interface personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..b9f2889db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export type { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..6ca5b82a2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + */ +export interface OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[] | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..c2fb7bc6a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3606939272); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..4f623616d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByID(2124352079); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(4281222271); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..d155adc30 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(3566862802); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..65098990f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByID(2590614085); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByID(773650321); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..76375250e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByID(1411160069, $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..d27e71304 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..34c4d151a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..d27e71304 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByID(4249972365); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..3ab21d612 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..50e62daa4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(3568225479); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..ef5015467 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByID(881980169, $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByID(3678582682, $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByID(319259595, $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(383995060, $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..ef5015467 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByID(3862002418, $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByID(2424639793, $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByID(3132595881, $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByID(2182412247, $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByID(3306292566, $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1754277916, $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1909469092, $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(4251088558, $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByID(1343888303, $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2205561041, $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByID(572240879, $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(2189402897, $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByID(642881729, $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1066151743, $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByID(2718999663, $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(2386486356, $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByID(2163571325, $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByID(2900172572, $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByID(881980169, $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByID(1075577233); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByID(3589606958, $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByID(224675106, $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByID(2124953624, $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByID(3516977899, $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByID(229603958, $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByID(3678582682, $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByID(319259595, $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(383995060, $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByID(1091960237, $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByID(3835643147, $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByID(2447692557, $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByID(2943477349, $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(3401034892, $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1236957573, $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(1160383782, $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1739300671, $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(793803239, $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1403757716, $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2988345717, $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(518250834, $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByID(2836661285, $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByID(1367187362, $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..8a2cb7a70 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..8519667d5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByID(1411160069, name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByID(1661412647, name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f29117203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,15 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..79c8907f9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByID(1491748400); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts new file mode 100644 index 000000000..ba2885969 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + TextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts new file mode 100644 index 000000000..00ec01151 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Marshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts new file mode 100644 index 000000000..8e7f0b3f1 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/json/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Marshaler is the interface implemented by types that + * can marshal themselves into valid JSON. + */ +export type Marshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts new file mode 100644 index 000000000..51089b14d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/encoding/models.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * TextMarshaler is the interface implemented by an object that can + * marshal itself into a textual form. + * + * MarshalText encodes the receiver into UTF-8-encoded text and returns the result. + */ +export type TextMarshaler = any; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts new file mode 100644 index 000000000..d19c65d22 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/greetservice.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Get someone. + */ +export function Get(aliasValue: $models.Alias): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.Get", aliasValue); +} + +/** + * Apparently, aliases are all the rage right now. + */ +export function GetButAliased(p: $models.AliasedPerson): $CancellablePromise<$models.StrangelyAliasedPerson> { + return $Call.ByName("main.GreetService.GetButAliased", p); +} + +/** + * Get someone quite different. + */ +export function GetButDifferent(): $CancellablePromise<$models.GenericPerson> { + return $Call.ByName("main.GreetService.GetButDifferent"); +} + +export function GetButForeignPrivateAlias(): $CancellablePromise { + return $Call.ByName("main.GreetService.GetButForeignPrivateAlias"); +} + +export function GetButGenericAliases(): $CancellablePromise<$models.AliasGroup> { + return $Call.ByName("main.GreetService.GetButGenericAliases"); +} + +/** + * Greet a lot of unusual things. + */ +export function Greet($0: $models.EmptyAliasStruct, $1: $models.EmptyStruct): $CancellablePromise<$models.AliasStruct> { + return $Call.ByName("main.GreetService.Greet", $0, $1); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts new file mode 100644 index 000000000..75cbdc737 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/index.ts @@ -0,0 +1,26 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + AliasGroup, + AliasStruct, + AliasedPerson, + EmptyAliasStruct, + EmptyStruct, + GenericAlias, + GenericMapAlias, + GenericPerson, + GenericPersonAlias, + GenericPtrAlias, + IndirectPersonAlias, + OtherAliasStruct, + Person, + StrangelyAliasedPerson, + TPIndirectPersonAlias +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts new file mode 100644 index 000000000..26b204c1f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/aliases/models.ts @@ -0,0 +1,125 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * A nice type Alias. + */ +export type Alias = number; + +/** + * A class whose fields have various aliased types. + */ +export interface AliasGroup { + "GAi": GenericAlias; + "GAP": GenericAlias>; + "GPAs": GenericPtrAlias; + "GPAP": GenericPtrAlias>; + "GMA": GenericMapAlias; + "GPA": GenericPersonAlias; + "IPA": IndirectPersonAlias; + "TPIPA": TPIndirectPersonAlias; +} + +/** + * A struct alias. + * This should be rendered as a typedef or interface in every mode. + */ +export interface AliasStruct { + /** + * A field with a comment. + */ + "Foo": number[] | null; + + /** + * Definitely not Foo. + */ + "Bar"?: string; + "Baz"?: string; + + /** + * A nested alias struct. + */ + "Other": OtherAliasStruct; +} + +/** + * A class alias. + */ +export type AliasedPerson = Person; + +/** + * An empty struct alias. + */ +export interface EmptyAliasStruct { +} + +/** + * An empty struct. + */ +export interface EmptyStruct { +} + +/** + * A generic alias that forwards to a type parameter. + */ +export type GenericAlias = T; + +/** + * A generic alias that wraps a map. + */ +export type GenericMapAlias = { [_: string]: U } | null; + +/** + * A generic struct containing an alias. + */ +export interface GenericPerson { + "Name": T; + "AliasedField": Alias; +} + +/** + * A generic alias that wraps a generic struct. + */ +export type GenericPersonAlias = GenericPerson[] | null>; + +/** + * A generic alias that wraps a pointer type. + */ +export type GenericPtrAlias = GenericAlias | null; + +/** + * An alias that wraps a class through a non-typeparam alias. + */ +export type IndirectPersonAlias = GenericPersonAlias; + +/** + * Another struct alias. + */ +export interface OtherAliasStruct { + "NoMoreIdeas": number[] | null; +} + +/** + * A non-generic struct containing an alias. + */ +export interface Person { + /** + * The Person's name. + */ + "Name": string; + + /** + * A random alias field. + */ + "AliasedField": Alias; +} + +/** + * Another class alias, but ordered after its aliased class. + */ +export type StrangelyAliasedPerson = Person; + +/** + * An alias that wraps a class through a typeparam alias. + */ +export type TPIndirectPersonAlias = GenericAlias>; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts new file mode 100644 index 000000000..bdcf43c67 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service7 from "./service7.js"; +import * as Service9 from "./service9.js"; +export { + Service7, + Service9 +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts new file mode 100644 index 000000000..e4281d925 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service7.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service7.TestMethod"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts new file mode 100644 index 000000000..1c0154b5e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config/service9.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function TestMethod2(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/complex_expressions/config.Service9.TestMethod2"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts new file mode 100644 index 000000000..9eff81e94 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(person: $models.Person, emb: $models.Embedded1): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", person, emb); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts new file mode 100644 index 000000000..f645e5730 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Title +} from "./models.js"; + +export type { + Embedded1, + Embedded3, + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts new file mode 100644 index 000000000..7cdb88164 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_json/models.ts @@ -0,0 +1,121 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Embedded1 { + /** + * Friends should be shadowed in Person by a field of lesser depth + */ + "Friends": number; + + /** + * Vanish should be omitted from Person because there is another field with same depth and no tag + */ + "Vanish": number; + + /** + * StillThere should be shadowed in Person by other field with same depth and a json tag + */ + "StillThere": string; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; +} + +export type Embedded3 = string; + +/** + * Person represents a person + */ +export interface Person { + /** + * Titles is optional in JSON + */ + "Titles"?: Title[] | null; + + /** + * Names has a + * multiline comment + */ + "Names": string[] | null; + + /** + * Partner has a custom and complex JSON key + */ + "Partner": Person | null; + "Friends": (Person | null)[] | null; + + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * StillThereButRenamed should shadow in Person the other field with same depth and no json tag + */ + "StillThere": Embedded3 | null; + + /** + * StrangeNumber maps to "-" + */ + "-": number; + + /** + * Embedded3 should appear with key "Embedded3" + */ + "Embedded3": Embedded3; + + /** + * StrangerNumber is serialized as a string + */ + "StrangerNumber": `${number}`; + + /** + * StrangestString is optional and serialized as a JSON string + */ + "StrangestString"?: `"${string}"`; + + /** + * StringStrangest is serialized as a JSON string and optional + */ + "StringStrangest"?: `"${string}"`; + + /** + * embedded4 should be optional and appear with key "emb4" + */ + "emb4"?: embedded4; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; + +export interface embedded4 { + /** + * NamingThingsIsHard is a law of programming + */ + "NamingThingsIsHard": `${boolean}`; + + /** + * Friends should not be shadowed in Person as embedded4 is not embedded + * from encoding/json's point of view; + * however, it should be shadowed in Embedded1 + */ + "Friends": boolean; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts new file mode 100644 index 000000000..0c3ef75cb --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/greetservice.ts @@ -0,0 +1,24 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + * It has a multiline doc comment + * The comment has even some * / traps!! + */ +export function Greet(str: string, people: $models.Person[] | null, $2: {"AnotherCount": number, "AnotherOne": $models.Person | null}, assoc: { [_: `${number}`]: boolean | null } | null, $4: (number | null)[] | null, ...other: string[]): $CancellablePromise<[$models.Person, any, number[] | null]> { + return $Call.ByName("main.GreetService.Greet", str, people, $2, assoc, $4, other); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts new file mode 100644 index 000000000..6691fb86c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/complex_method/models.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Person represents a person + */ +export interface Person { + "Name": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts new file mode 100644 index 000000000..52f87997c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.StructA, $models.StructC]> { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts new file mode 100644 index 000000000..bbf592890 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + StructA, + StructC, + StructE +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts new file mode 100644 index 000000000..256987ac8 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_imports/models.ts @@ -0,0 +1,21 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface StructA { + "B": structB | null; +} + +export interface StructC { + "D": structD; +} + +export interface StructE { +} + +export interface structB { + "A": StructA | null; +} + +export interface structD { + "E": StructE; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts new file mode 100644 index 000000000..b7ef0ae3e --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Make a cycle. + */ +export function MakeCycles(): $CancellablePromise<[$models.Cyclic, $models.GenericCyclic<$models.GenericCyclic>]> { + return $Call.ByName("main.GreetService.MakeCycles"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts new file mode 100644 index 000000000..16cef660c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Alias, + Cyclic, + GenericCyclic +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts new file mode 100644 index 000000000..5395305fc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/cyclic_types/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type Alias = Cyclic | null; + +export type Cyclic = ({ [_: string]: Alias } | null)[] | null; + +export type GenericCyclic = {"X": GenericCyclic | null, "Y": T[] | null}[] | null; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts new file mode 100644 index 000000000..bb2426022 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/index.ts @@ -0,0 +1,13 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +console.log("Hello everywhere!"); +console.log("Hello everywhere again!"); +console.log("Hello Interfaces!"); +console.log("Hello TS!"); +console.log("Hello TS Interfaces!"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts new file mode 100644 index 000000000..faca61bc2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/internalservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.InternalModel): $CancellablePromise { + return $Call.ByName("main.InternalService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts new file mode 100644 index 000000000..efe76e58b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/models.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An exported but internal model. + */ +export interface InternalModel { + "Field": string; +} + +/** + * An unexported model. + */ +export interface unexportedModel { + "Field": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts new file mode 100644 index 000000000..82d89784a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type { + Dummy +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts new file mode 100644 index 000000000..71070d754 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/otherpackage/models.ts @@ -0,0 +1,5 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Dummy { +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts new file mode 100644 index 000000000..84ac0538c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/service.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as otherpackage$0 from "./otherpackage/models.js"; + +function InternalMethod($0: string): $CancellablePromise { + return $Call.ByName("main.Service.InternalMethod", $0); +} + +export function VisibleMethod($0: otherpackage$0.Dummy): $CancellablePromise { + return $Call.ByName("main.Service.VisibleMethod", $0); +} + +export async function CustomMethod(arg: string): Promise { + await InternalMethod("Hello " + arg + "!"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js new file mode 100644 index 000000000..138385f53 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js new file mode 100644 index 000000000..19d5c2f42 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_all.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("everywhere again"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js new file mode 100644 index 000000000..442f20472 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_i.js @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts new file mode 100644 index 000000000..253d3f2f6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_t.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts new file mode 100644 index 000000000..7400e97aa --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/test_ti.ts @@ -0,0 +1,3 @@ +import { CustomMethod } from "./service.js"; + +CustomMethod("TS Interfaces"); diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts new file mode 100644 index 000000000..b49239ada --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/directives/unexportedservice.ts @@ -0,0 +1,19 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * An unexported service. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method($0: $models.unexportedModel): $CancellablePromise { + return $Call.ByName("main.unexportedService.Method", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts new file mode 100644 index 000000000..4951af01d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/greetservice.ts @@ -0,0 +1,47 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Comment 1. + */ +export function Method1(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method1"); +} + +/** + * Comment 2. + */ +export function Method2(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method2"); +} + +/** + * Comment 3a. + * Comment 3b. + */ +export function Method3(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method3"); +} + +/** + * Comment 4. + */ +export function Method4(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method4"); +} + +/** + * Comment 5. + */ +export function Method5(): $CancellablePromise { + return $Call.ByName("main.GreetService.Method5"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/embedded_interface/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts new file mode 100644 index 000000000..d857aa9e7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: $models.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts new file mode 100644 index 000000000..e66941c16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/index.ts @@ -0,0 +1,16 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export { + Age, + Title +} from "./models.js"; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts new file mode 100644 index 000000000..74d673e16 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum/models.ts @@ -0,0 +1,55 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; + +/** + * Person represents a person + */ +export interface Person { + "Title": Title; + "Name": string; + "Age": Age; +} + +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts new file mode 100644 index 000000000..73469c39d --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/greetservice.ts @@ -0,0 +1,22 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string, title: services$0.Title): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name, title); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts new file mode 100644 index 000000000..01c612edc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Title +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts new file mode 100644 index 000000000..887aee9ba --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/enum_from_imported_package/services/models.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts new file mode 100644 index 000000000..99b989f07 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/models.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts new file mode 100644 index 000000000..c6db3803b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_imported_package/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts new file mode 100644 index 000000000..70b85519b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./services/other/models.js"; + +export interface Person { + "Name": string; + "Address": other$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts new file mode 100644 index 000000000..b83ce5234 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/function_from_nested_imported_package/services/other.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_multiple_files/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts new file mode 100644 index 000000000..63a65248c --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/greetservice.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * Greet someone + */ +export function GreetWithContext(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.GreetWithContext", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_context/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts new file mode 100644 index 000000000..e3aeb8a64 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/index.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + BasicCstrAlias, + ComparableCstrAlias, + EmbeddedCustomInterface, + EmbeddedOriginalInterface, + EmbeddedPointer, + EmbeddedPointerPtr, + EmbeddedValue, + EmbeddedValuePtr, + GoodTildeCstrAlias, + InterfaceCstrAlias, + Maps, + MixedCstrAlias, + NonBasicCstrAlias, + PointableCstrAlias, + PointerAlias, + PointerTextMarshaler, + StringAlias, + StringType, + ValueAlias, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts new file mode 100644 index 000000000..aaabd3502 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/models.ts @@ -0,0 +1,767 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export type BasicCstrAlias = S; + +export type ComparableCstrAlias = R; + +export type EmbeddedCustomInterface = string; + +export type EmbeddedOriginalInterface = string; + +export type EmbeddedPointer = string; + +export type EmbeddedPointerPtr = string; + +export type EmbeddedValue = string; + +export type EmbeddedValuePtr = string; + +export type GoodTildeCstrAlias = U; + +export type InterfaceCstrAlias = Y; + +export interface Maps { + /** + * Reject + */ + "Bool": { [_: string]: number } | null; + + /** + * Accept + */ + "Int": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Uint": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "Float": { [_: string]: number } | null; + + /** + * Reject + */ + "Complex": { [_: string]: number } | null; + + /** + * Accept + */ + "Byte": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "Rune": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "String": { [_: string]: number } | null; + + /** + * Reject + */ + "IntPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "UintPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "FloatPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "ComplexPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "StringPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "NTM": { [_: string]: number } | null; + + /** + * Reject + */ + "NTMPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VTM": { [_: ValueTextMarshaler]: number } | null; + + /** + * Accept + */ + "VTMPtr": { [_: ValueTextMarshaler]: number } | null; + + /** + * Reject + */ + "PTM": { [_: string]: number } | null; + + /** + * Accept + */ + "PTMPtr": { [_: PointerTextMarshaler]: number } | null; + + /** + * Accept, hide + */ + "JTM": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "JTMPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "A": { [_: string]: number } | null; + + /** + * Reject + */ + "APtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TM": { [_: string]: number } | null; + + /** + * Reject + */ + "TMPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "CI": { [_: string]: number } | null; + + /** + * Reject + */ + "CIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "EI": { [_: string]: number } | null; + + /** + * Reject + */ + "EIPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "EV": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVPtr": { [_: EmbeddedValue]: number } | null; + + /** + * Accept + */ + "EVP": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Accept + */ + "EVPPtr": { [_: EmbeddedValuePtr]: number } | null; + + /** + * Reject + */ + "EP": { [_: string]: number } | null; + + /** + * Accept + */ + "EPPtr": { [_: EmbeddedPointer]: number } | null; + + /** + * Accept + */ + "EPP": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "EPPPtr": { [_: EmbeddedPointerPtr]: number } | null; + + /** + * Accept + */ + "ECI": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "ECIPtr": { [_: EmbeddedCustomInterface]: number } | null; + + /** + * Accept + */ + "EOI": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Accept + */ + "EOIPtr": { [_: EmbeddedOriginalInterface]: number } | null; + + /** + * Reject + */ + "WT": { [_: string]: number } | null; + + /** + * Reject + */ + "WA": { [_: string]: number } | null; + + /** + * Accept + */ + "ST": { [_: StringType]: number } | null; + + /** + * Accept + */ + "SA": { [_: StringAlias]: number } | null; + + /** + * Accept + */ + "IntT": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "IntA": { [_: `${number}`]: number } | null; + + /** + * Reject + */ + "VT": { [_: string]: number } | null; + + /** + * Reject + */ + "VTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "VPT": { [_: string]: number } | null; + + /** + * Reject + */ + "VPTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "VA": { [_: ValueAlias]: number } | null; + + /** + * Accept + */ + "VAPtr": { [_: ValueAlias]: number } | null; + + /** + * Accept, hide + */ + "VPA": { [_: string]: number } | null; + + /** + * Reject + */ + "VPAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PT": { [_: string]: number } | null; + + /** + * Reject + */ + "PTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PPT": { [_: string]: number } | null; + + /** + * Reject + */ + "PPTPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "PA": { [_: string]: number } | null; + + /** + * Accept + */ + "PAPtr": { [_: PointerAlias]: number } | null; + + /** + * Accept, hide + */ + "PPA": { [_: string]: number } | null; + + /** + * Reject + */ + "PPAPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IT": { [_: string]: number } | null; + + /** + * Reject + */ + "ITPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPT": { [_: string]: number } | null; + + /** + * Reject + */ + "IPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "IA": { [_: string]: number } | null; + + /** + * Reject + */ + "IAPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "IPA": { [_: string]: number } | null; + + /** + * Reject + */ + "IPAPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPRPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPSPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPTPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "TPZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "TPZPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAR": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GARPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAS": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GASPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAT": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GATPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAU": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAUPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAV": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAVPtr": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAW": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAWPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAX": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAXPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAY": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAYPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAZ": { [_: string]: number } | null; + + /** + * Soft reject + */ + "GAZPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GACV": { [_: ComparableCstrAlias]: number } | null; + + /** + * Reject + */ + "GACP": { [_: string]: number } | null; + + /** + * Reject + */ + "GACiPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GACPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GABi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GABs": { [_: BasicCstrAlias]: number } | null; + + /** + * Reject + */ + "GABiPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GABT": { [_: string]: number } | null; + + /** + * Reject + */ + "GABTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAGT": { [_: GoodTildeCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAGTPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GANBV": { [_: NonBasicCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GANBP": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GANBVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GANBPPtr": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlV1": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept + */ + "GAPlV2": { [_: PointableCstrAlias]: number } | null; + + /** + * Reject + */ + "GAPlP1": { [_: string]: number } | null; + + /** + * Accept + */ + "GAPlP2": { [_: PointableCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAPlVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPlPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMi": { [_: `${number}`]: number } | null; + + /** + * Accept + */ + "GAMS": { [_: MixedCstrAlias]: number } | null; + + /** + * Accept + */ + "GAMV": { [_: MixedCstrAlias]: number } | null; + + /** + * Reject + */ + "GAMSPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAMVPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAII": { [_: string]: number } | null; + + /** + * Accept + */ + "GAIV": { [_: InterfaceCstrAlias]: number } | null; + + /** + * Accept, hide + */ + "GAIP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIIPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAIVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAIPPtr": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrV": { [_: string]: number } | null; + + /** + * Accept, hide + */ + "GAPrP": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrVPtr": { [_: string]: number } | null; + + /** + * Reject + */ + "GAPrPPtr": { [_: string]: number } | null; +} + +export type MixedCstrAlias = X; + +export type NonBasicCstrAlias = V; + +export type PointableCstrAlias = W; + +export type PointerAlias = PointerTextMarshaler; + +export type PointerTextMarshaler = string; + +export type StringAlias = string; + +export type StringType = string; + +export type ValueAlias = ValueTextMarshaler; + +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts new file mode 100644 index 000000000..7b59b53ed --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Maps<$models.PointerTextMarshaler, number, number, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null, $models.ValueTextMarshaler, $models.StringType, $models.ValueTextMarshaler, $models.PointerTextMarshaler | null>> { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts new file mode 100644 index 000000000..7c55b767b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/index.ts @@ -0,0 +1,33 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Service from "./service.js"; +export { + Service +}; + +export type { + AliasJsonMarshaler, + AliasMarshaler, + AliasNonMarshaler, + AliasTextMarshaler, + Data, + ImplicitJsonButText, + ImplicitJsonMarshaler, + ImplicitMarshaler, + ImplicitNonJson, + ImplicitNonMarshaler, + ImplicitNonText, + ImplicitTextButJson, + ImplicitTextMarshaler, + NonMarshaler, + PointerJsonMarshaler, + PointerMarshaler, + PointerTextMarshaler, + UnderlyingJsonMarshaler, + UnderlyingMarshaler, + UnderlyingTextMarshaler, + ValueJsonMarshaler, + ValueMarshaler, + ValueTextMarshaler +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts new file mode 100644 index 000000000..02d639994 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/models.ts @@ -0,0 +1,309 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as encoding$0 from "../../../../../../../../encoding/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as json$0 from "../../../../../../../../encoding/json/models.js"; + +/** + * any + */ +export type AliasJsonMarshaler = any; + +/** + * any + */ +export type AliasMarshaler = any; + +/** + * struct{} + */ +export interface AliasNonMarshaler { +} + +/** + * string + */ +export type AliasTextMarshaler = string; + +export interface Data { + "NM": NonMarshaler; + + /** + * NonMarshaler | null + */ + "NMPtr": NonMarshaler | null; + "VJM": ValueJsonMarshaler; + + /** + * ValueJsonMarshaler | null + */ + "VJMPtr": ValueJsonMarshaler | null; + "PJM": PointerJsonMarshaler; + + /** + * PointerJsonMarshaler | null + */ + "PJMPtr": PointerJsonMarshaler | null; + "VTM": ValueTextMarshaler; + + /** + * ValueTextMarshaler | null + */ + "VTMPtr": ValueTextMarshaler | null; + "PTM": PointerTextMarshaler; + + /** + * PointerTextMarshaler | null + */ + "PTMPtr": PointerTextMarshaler | null; + "VM": ValueMarshaler; + + /** + * ValueMarshaler | null + */ + "VMPtr": ValueMarshaler | null; + "PM": PointerMarshaler; + + /** + * PointerMarshaler | null + */ + "PMPtr": PointerMarshaler | null; + "UJM": UnderlyingJsonMarshaler; + + /** + * UnderlyingJsonMarshaler | null + */ + "UJMPtr": UnderlyingJsonMarshaler | null; + "UTM": UnderlyingTextMarshaler; + + /** + * UnderlyingTextMarshaler | null + */ + "UTMPtr": UnderlyingTextMarshaler | null; + "UM": UnderlyingMarshaler; + + /** + * UnderlyingMarshaler | null + */ + "UMPtr": UnderlyingMarshaler | null; + + /** + * any + */ + "JM": any; + + /** + * any | null + */ + "JMPtr": any | null; + + /** + * string + */ + "TM": string; + + /** + * string | null + */ + "TMPtr": string | null; + + /** + * any + */ + "CJM": any; + + /** + * any | null + */ + "CJMPtr": any | null; + + /** + * string + */ + "CTM": string; + + /** + * string | null + */ + "CTMPtr": string | null; + + /** + * any + */ + "CM": any; + + /** + * any | null + */ + "CMPtr": any | null; + "ANM": AliasNonMarshaler; + + /** + * AliasNonMarshaler | null + */ + "ANMPtr": AliasNonMarshaler | null; + "AJM": AliasJsonMarshaler; + + /** + * AliasJsonMarshaler | null + */ + "AJMPtr": AliasJsonMarshaler | null; + "ATM": AliasTextMarshaler; + + /** + * AliasTextMarshaler | null + */ + "ATMPtr": AliasTextMarshaler | null; + "AM": AliasMarshaler; + + /** + * AliasMarshaler | null + */ + "AMPtr": AliasMarshaler | null; + "ImJM": ImplicitJsonMarshaler; + + /** + * ImplicitJsonMarshaler | null + */ + "ImJMPtr": ImplicitJsonMarshaler | null; + "ImTM": ImplicitTextMarshaler; + + /** + * ImplicitTextMarshaler | null + */ + "ImTMPtr": ImplicitTextMarshaler | null; + "ImM": ImplicitMarshaler; + + /** + * ImplicitMarshaler | null + */ + "ImMPtr": ImplicitMarshaler | null; + "ImNJ": ImplicitNonJson; + + /** + * ImplicitNonJson | null + */ + "ImNJPtr": ImplicitNonJson | null; + "ImNT": ImplicitNonText; + + /** + * ImplicitNonText | null + */ + "ImNTPtr": ImplicitNonText | null; + "ImNM": ImplicitNonMarshaler; + + /** + * ImplicitNonMarshaler | null + */ + "ImNMPtr": ImplicitNonMarshaler | null; + "ImJbT": ImplicitJsonButText; + + /** + * ImplicitJsonButText | null + */ + "ImJbTPtr": ImplicitJsonButText | null; + "ImTbJ": ImplicitTextButJson; + + /** + * ImplicitTextButJson | null + */ + "ImTbJPtr": ImplicitTextButJson | null; +} + +/** + * any + */ +export type ImplicitJsonButText = any; + +/** + * any + */ +export type ImplicitJsonMarshaler = any; + +/** + * any + */ +export type ImplicitMarshaler = any; + +/** + * string + */ +export type ImplicitNonJson = string; + +/** + * class{ Marshaler, TextMarshaler } + */ +export interface ImplicitNonMarshaler { + "Marshaler": json$0.Marshaler; + "TextMarshaler": encoding$0.TextMarshaler; +} + +/** + * any + */ +export type ImplicitNonText = any; + +/** + * any + */ +export type ImplicitTextButJson = any; + +/** + * string + */ +export type ImplicitTextMarshaler = string; + +/** + * class {} + */ +export interface NonMarshaler { +} + +/** + * any + */ +export type PointerJsonMarshaler = any; + +/** + * any + */ +export type PointerMarshaler = any; + +/** + * string + */ +export type PointerTextMarshaler = string; + +/** + * any + */ +export type UnderlyingJsonMarshaler = any; + +/** + * any + */ +export type UnderlyingMarshaler = any; + +/** + * string + */ +export type UnderlyingTextMarshaler = string; + +/** + * any + */ +export type ValueJsonMarshaler = any; + +/** + * any + */ +export type ValueMarshaler = any; + +/** + * string + */ +export type ValueTextMarshaler = string; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts new file mode 100644 index 000000000..0de2fecd5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/marshalers/service.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function Method(): $CancellablePromise<$models.Data> { + return $Call.ByName("main.Service.Method"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts new file mode 100644 index 000000000..bf611e486 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/index.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as SomeMethods from "./somemethods.js"; +export { + SomeMethods +}; + +export type { + HowDifferent, + Impersonator, + Person, + PrivatePerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts new file mode 100644 index 000000000..c42cce373 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/models.ts @@ -0,0 +1,64 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as other$0 from "./other/models.js"; + +/** + * HowDifferent is a curious kind of person + * that lets other people decide how they are different. + */ +export interface HowDifferent { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": ({ [_: string]: How } | null)[] | null; +} + +/** + * Impersonator gets their fields from other people. + */ +export type Impersonator = other$0.OtherPerson; + +/** + * Person is not a number. + */ +export interface Person { + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} + +/** + * PrivatePerson gets their fields from hidden sources. + */ +export type PrivatePerson = personImpl; + +export interface personImpl { + /** + * Nickname conceals a person's identity. + */ + "Nickname": string; + + /** + * They have a name. + */ + "Name": string; + + /** + * Exactly 4 sketchy friends. + */ + "Friends": Impersonator[]; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts new file mode 100644 index 000000000..b9f2889db --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherMethods from "./othermethods.js"; +export { + OtherMethods +}; + +export type { + OtherPerson +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts new file mode 100644 index 000000000..6ca5b82a2 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/models.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherPerson is like a person, but different. + */ +export interface OtherPerson { + /** + * They have a name as well. + */ + "Name": string; + + /** + * But they may have many differences. + */ + "Differences": T[] | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts new file mode 100644 index 000000000..e6638332a --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other/othermethods.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherMethods has another method, but through a private embedded type. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/other.OtherMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts new file mode 100644 index 000000000..e37a596df --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/somemethods.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * SomeMethods exports some methods. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[$models.Person, $models.HowDifferent, $models.PrivatePerson]> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here.SomeMethods.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts new file mode 100644 index 000000000..ec7619bfc --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedother.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedOther is even trickier. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedOther.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts new file mode 100644 index 000000000..680b028b4 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/embedservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * EmbedService is tricky. + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as nobindingshere$0 from "../no_bindings_here/models.js"; + +/** + * LikeThisOne is an example method that does nothing. + */ +export function LikeThisOne(): $CancellablePromise<[nobindingshere$0.Person, nobindingshere$0.HowDifferent, nobindingshere$0.PrivatePerson]> { + return $Call.ByName("main.EmbedService.LikeThisOne"); +} + +/** + * LikeThisOtherOne does nothing as well, but is different. + */ +export function LikeThisOtherOne(): $CancellablePromise { + return $Call.ByName("main.EmbedService.LikeThisOtherOne"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts new file mode 100644 index 000000000..4166b32f7 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet($0: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", $0); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts new file mode 100644 index 000000000..e69bebf3b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/out_of_tree/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as EmbedOther from "./embedother.js"; +import * as EmbedService from "./embedservice.js"; +import * as GreetService from "./greetservice.js"; +export { + EmbedOther, + EmbedService, + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts new file mode 100644 index 000000000..5a733e6c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts new file mode 100644 index 000000000..41664b850 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/greetservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts new file mode 100644 index 000000000..5c4ebea4f --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/index.ts @@ -0,0 +1,9 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +import * as OtherService from "./otherservice.js"; +export { + GreetService, + OtherService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts new file mode 100644 index 000000000..5a733e6c9 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_files/otherservice.ts @@ -0,0 +1,10 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +export function Hello(): $CancellablePromise { + return $Call.ByName("main.OtherService.Hello"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..3ab21d612 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/models.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts new file mode 100644 index 000000000..018d8df30 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_multiple_other/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts new file mode 100644 index 000000000..b8abcb9a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_non_pointer_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts new file mode 100644 index 000000000..b8abcb9a6 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/greetservice.ts @@ -0,0 +1,190 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ArrayInt($in: number[]): $CancellablePromise { + return $Call.ByName("main.GreetService.ArrayInt", $in); +} + +export function BoolInBoolOut($in: boolean): $CancellablePromise { + return $Call.ByName("main.GreetService.BoolInBoolOut", $in); +} + +export function Float32InFloat32Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float32InFloat32Out", $in); +} + +export function Float64InFloat64Out($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Float64InFloat64Out", $in); +} + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +export function Int16InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16InIntOut", $in); +} + +export function Int16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int16PointerInAndOutput", $in); +} + +export function Int32InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32InIntOut", $in); +} + +export function Int32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int32PointerInAndOutput", $in); +} + +export function Int64InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64InIntOut", $in); +} + +export function Int64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int64PointerInAndOutput", $in); +} + +export function Int8InIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8InIntOut", $in); +} + +export function Int8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.Int8PointerInAndOutput", $in); +} + +export function IntInIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.IntInIntOut", $in); +} + +export function IntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInAndOutput", $in); +} + +export function IntPointerInputNamedOutputs($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.IntPointerInputNamedOutputs", $in); +} + +export function MapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntInt", $in); +} + +export function MapIntIntPointer($in: { [_: `${number}`]: number | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntIntPointer", $in); +} + +export function MapIntSliceInt($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.MapIntSliceInt", $in); +} + +export function MapIntSliceIntInMapIntSliceIntOut($in: { [_: `${number}`]: number[] | null } | null): $CancellablePromise<{ [_: `${number}`]: number[] | null } | null> { + return $Call.ByName("main.GreetService.MapIntSliceIntInMapIntSliceIntOut", $in); +} + +export function NoInputsStringOut(): $CancellablePromise { + return $Call.ByName("main.GreetService.NoInputsStringOut"); +} + +export function PointerBoolInBoolOut($in: boolean | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerBoolInBoolOut", $in); +} + +export function PointerFloat32InFloat32Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat32InFloat32Out", $in); +} + +export function PointerFloat64InFloat64Out($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerFloat64InFloat64Out", $in); +} + +export function PointerMapIntInt($in: { [_: `${number}`]: number } | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerMapIntInt", $in); +} + +export function PointerStringInStringOut($in: string | null): $CancellablePromise { + return $Call.ByName("main.GreetService.PointerStringInStringOut", $in); +} + +export function StringArrayInputNamedOutput($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutput", $in); +} + +export function StringArrayInputNamedOutputs($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputNamedOutputs", $in); +} + +export function StringArrayInputStringArrayOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringArrayOut", $in); +} + +export function StringArrayInputStringOut($in: string[] | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StringArrayInputStringOut", $in); +} + +export function StructInputStructOutput($in: $models.Person): $CancellablePromise<$models.Person> { + return $Call.ByName("main.GreetService.StructInputStructOutput", $in); +} + +export function StructPointerInputErrorOutput($in: $models.Person | null): $CancellablePromise { + return $Call.ByName("main.GreetService.StructPointerInputErrorOutput", $in); +} + +export function StructPointerInputStructPointerOutput($in: $models.Person | null): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.StructPointerInputStructPointerOutput", $in); +} + +export function UInt16InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16InUIntOut", $in); +} + +export function UInt16PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt16PointerInAndOutput", $in); +} + +export function UInt32InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32InUIntOut", $in); +} + +export function UInt32PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt32PointerInAndOutput", $in); +} + +export function UInt64InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64InUIntOut", $in); +} + +export function UInt64PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt64PointerInAndOutput", $in); +} + +export function UInt8InUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8InUIntOut", $in); +} + +export function UInt8PointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UInt8PointerInAndOutput", $in); +} + +export function UIntInUIntOut($in: number): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntInUIntOut", $in); +} + +export function UIntPointerInAndOutput($in: number | null): $CancellablePromise { + return $Call.ByName("main.GreetService.UIntPointerInAndOutput", $in); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts new file mode 100644 index 000000000..5664b79ab --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/struct_literal_single/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Person { + "Name": string; + "Parent": Person | null; + "Details": {"Age": number, "Address": {"Street": string}}; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts new file mode 100644 index 000000000..80bebe425 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/greetservice.ts @@ -0,0 +1,18 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +/** + * Greet someone + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts new file mode 100644 index 000000000..50e3f0435 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_function/index.ts @@ -0,0 +1,7 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts new file mode 100644 index 000000000..5a8e9adc3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/greetservice.ts @@ -0,0 +1,29 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * GreetService is great + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Greet does XYZ + */ +export function Greet(name: string): $CancellablePromise { + return $Call.ByName("main.GreetService.Greet", name); +} + +/** + * NewPerson creates a new person + */ +export function NewPerson(name: string): $CancellablePromise<$models.Person | null> { + return $Call.ByName("main.GreetService.NewPerson", name); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts new file mode 100644 index 000000000..14252454b --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GreetService from "./greetservice.js"; +export { + GreetService +}; + +export type { + Person +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..f29117203 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/models.ts @@ -0,0 +1,15 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as services$0 from "./services/models.js"; + +/** + * Person is a person! + * They have a name and an address + */ +export interface Person { + "Name": string; + "Address": services$0.Address | null; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts new file mode 100644 index 000000000..8857d2bc5 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/index.ts @@ -0,0 +1,11 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as OtherService from "./otherservice.js"; +export { + OtherService +}; + +export type { + Address +} from "./models.js"; diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts new file mode 100644 index 000000000..b76080cf3 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/models.ts @@ -0,0 +1,8 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export interface Address { + "Street": string; + "State": string; + "Country": string; +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts new file mode 100644 index 000000000..c93f85314 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services/otherservice.ts @@ -0,0 +1,23 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * OtherService is a struct + * that does things + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise } from "/wails/runtime.js"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +/** + * Yay does this and that + */ +export function Yay(): $CancellablePromise<$models.Address | null> { + return $Call.ByName("github.com/wailsapp/wails/v3/internal/generator/testcases/variable_single_from_other_function/services.OtherService.Yay"); +} diff --git a/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log new file mode 100644 index 000000000..e802b6b63 --- /dev/null +++ b/v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/warnings.log @@ -0,0 +1,70 @@ +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *U is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *V is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *X is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Y is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *Z is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *encoding.TextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.CustomInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedInterface is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *int is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *string is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *uint is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type W is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type any is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type bool is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type complex64 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type float32 is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[T] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BadTildeCstrPtrAlias[struct{}] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[S] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.BasicCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[R] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ComparableCstrPtrAlias[int] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.EmbeddedPointer is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.GoodTildeCstrPtrAlias[U] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[Y] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfaceCstrPtrAlias[encoding.TextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.InterfacePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[X] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.MixedCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.StringType] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[*github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonBasicCstrPtrAlias[V] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.NonTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[W] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointableCstrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[R, Z] is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerCstrPtrAlias[github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler, *github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueTextMarshaler] is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerPtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerTextMarshaler is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.PointerType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValuePtrType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.ValueType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongAlias is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors +package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys.WrongType is used as a map key, but does not implement encoding.TextMarshaler: this will likely result in runtime errors diff --git a/v3/internal/generator/testdata/package-lock.json b/v3/internal/generator/testdata/package-lock.json new file mode 100644 index 000000000..0e30f5fd3 --- /dev/null +++ b/v3/internal/generator/testdata/package-lock.json @@ -0,0 +1,2272 @@ +{ + "name": "testdata", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testdata", + "version": "0.0.0", + "devDependencies": { + "madge": "^8.0.0", + "typescript": "^5.7.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dependents/detective-less": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", + "integrity": "sha512-D/9dozteKcutI5OdxJd8rU+fL6XgaaRg60sPPJWkT33OCiRfkCu5wO5B/yXTaaL2e6EB0lcCBGe5E0XscZCvvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@ts-graphviz/adapter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz", + "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/ast": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.7.tgz", + "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/common": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz", + "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/core": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.7.tgz", + "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-module-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.0.tgz", + "integrity": "sha512-LFRg7178Fw5R4FAEwZxVqiRI8IxSM+Ay2UBrHoCerXNme+kMMMfz7T3xDGV/c2fer87hcrtgJGsnSOfUrPK6ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dependency-tree": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.0.1.tgz", + "integrity": "sha512-eCt7HSKIC9NxgIykG2DRq3Aewn9UhVS14MB3rEn6l/AsEI1FBg6ZGSlCU0SZ6Tjm2kkhj6/8c2pViinuyKELhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0", + "filing-cabinet": "^5.0.1", + "precinct": "^12.0.2", + "typescript": "^5.4.5" + }, + "bin": { + "dependency-tree": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-tree/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-amd": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.0.tgz", + "integrity": "sha512-NTqfYfwNsW7AQltKSEaWR66hGkTeD52Kz3eRQ+nfkA9ZFZt3iifRCWh+yZ/m6t3H42JFwVFTrml/D64R2PAIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "escodegen": "^2.1.0", + "get-amd-module-type": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "detective-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-cjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.0.tgz", + "integrity": "sha512-R55jTS6Kkmy6ukdrbzY4x+I7KkXiuDPpFzUViFV/tm2PBGtTCjkh9ZmTuJc1SaziMHJOe636dtiZLEuzBL9drg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-es6": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.0.tgz", + "integrity": "sha512-NGTnzjvgeMW1khUSEXCzPDoraLenWbUjCFjwxReH+Ir+P6LGjYtaBbAvITWn2H0VSC+eM7/9LFOTAkrta6hNYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-postcss": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.0.tgz", + "integrity": "sha512-pSXA6dyqmBPBuERpoOKKTUUjQCZwZPLRbd1VdsTbt6W+m/+6ROl4BbE87yQBUtLoK7yX8pvXHdKyM/xNIW9F7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-url": "^1.2.4", + "postcss-values-parser": "^6.0.2" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/detective-sass": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.0.tgz", + "integrity": "sha512-h5GCfFMkPm4ZUUfGHVPKNHKT8jV7cSmgK+s4dgQH4/dIUNh9/huR1fjEQrblOQNDalSU7k7g+tiW9LJ+nVEUhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-scss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.0.tgz", + "integrity": "sha512-Y64HyMqntdsCh1qAH7ci95dk0nnpA29g319w/5d/oYcHolcGUVJbIhOirOFjfN1KnMAXAFm5FIkZ4l2EKFGgxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-stylus": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.0.tgz", + "integrity": "sha512-KMHOsPY6aq3196WteVhkY5FF+6Nnc/r7q741E+Gq+Ax9mhE2iwj8Hlw8pl+749hPDRDBHZ2WlgOjP+twIG61vQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-typescript": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-13.0.0.tgz", + "integrity": "sha512-tcMYfiFWoUejSbvSblw90NDt76/4mNftYCX0SMnVRYzSXv8Fvo06hi4JOPdNvVNxRtCAKg3MJ3cBJh+ygEMH+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "^7.6.0", + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/detective-vue2": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.1.1.tgz", + "integrity": "sha512-/TQ+cs4qmSyhgESjyBXxoUuh36XjS06+UhCItWcGGOpXmU3KBRGRknG+tDzv2dASn1+UJUm2rhpDFa9TWT0dFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.0", + "@vue/compiler-sfc": "^3.5.13", + "detective-es6": "^5.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/filing-cabinet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.2.tgz", + "integrity": "sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-module-path": "^2.2.0", + "commander": "^12.0.0", + "enhanced-resolve": "^5.16.0", + "module-definition": "^6.0.0", + "module-lookup-amd": "^9.0.1", + "resolve": "^1.22.8", + "resolve-dependency-path": "^4.0.0", + "sass-lookup": "^6.0.1", + "stylus-lookup": "^6.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.4" + }, + "bin": { + "filing-cabinet": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/filing-cabinet/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-amd-module-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.0.tgz", + "integrity": "sha512-hFM7oivtlgJ3d6XWD6G47l8Wyh/C6vFw5G24Kk1Tbq85yh5gcM8Fne5/lFhiuxB+RT6+SI7I1ThB9lG4FBh3jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", + "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^11.0.0", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^2.1.2", + "walkdir": "^0.4.1" + }, + "bin": { + "madge": "bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/pahen" + }, + "peerDependencies": { + "typescript": "^5.4.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/module-definition": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.0.tgz", + "integrity": "sha512-sEGP5nKEXU7fGSZUML/coJbrO+yQtxcppDAYWRE9ovWsTbFoUHB2qDUx564WUzDaBHXsD46JBbIK5WVTwCyu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "module-definition": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.2.tgz", + "integrity": "sha512-p7PzSVEWiW9fHRX9oM+V4aV5B2nCVddVNv4DZ/JB6t9GsXY4E+ZVhPpnwUX7bbJyGeeVZqhS8q/JZ/H77IqPFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "glob": "^7.2.3", + "requirejs": "^2.3.7", + "requirejs-config-file": "^4.0.0" + }, + "bin": { + "lookup-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-source-walk": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.0.tgz", + "integrity": "sha512-1uiY543L+N7Og4yswvlm5NCKgPKDEXd9AUR9Jh3gen6oOeBsesr6LqhXom1er3eRzSUcVRWXzhv8tSNrIfGHKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.24.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.2.9" + } + }, + "node_modules/precinct": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.1.2.tgz", + "integrity": "sha512-x2qVN3oSOp3D05ihCd8XdkIPuEQsyte7PSxzLqiRgktu79S5Dr1I75/S+zAup8/0cwjoiJTQztE9h0/sWp9bJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.0", + "commander": "^12.1.0", + "detective-amd": "^6.0.0", + "detective-cjs": "^6.0.0", + "detective-es6": "^5.0.0", + "detective-postcss": "^7.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0", + "detective-vue2": "^2.0.3", + "module-definition": "^6.0.0", + "node-source-walk": "^7.0.0", + "postcss": "^8.4.40", + "typescript": "^5.5.4" + }, + "bin": { + "precinct": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/precinct/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/requirejs": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz", + "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==", + "dev": true, + "license": "MIT", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dependency-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.0.tgz", + "integrity": "sha512-hlY1SybBGm5aYN3PC4rp15MzsJLM1w+MEA/4KU3UBPfz4S0lL3FL6mgv7JgaA8a+ZTeEQAiF1a1BuN2nkqiIlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sass-lookup": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.0.1.tgz", + "integrity": "sha512-nl9Wxbj9RjEJA5SSV0hSDoU2zYGtE+ANaDS4OFUR7nYrquvBFvPKZZtQHe3lvnxCcylEDV00KUijjdMTUElcVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "sass-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sass-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylus-lookup": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.0.0.tgz", + "integrity": "sha512-RaWKxAvPnIXrdby+UWCr1WRfa+lrPMSJPySte4Q6a+rWyjeJyFOLJxr5GrAVfcMCsfVlCuzTAJ/ysYT8p8do7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "stylus-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylus-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-graphviz": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.6.tgz", + "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.6", + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5", + "@ts-graphviz/core": "^2.0.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/v3/internal/generator/testdata/package.json b/v3/internal/generator/testdata/package.json new file mode 100644 index 000000000..4ad95cc64 --- /dev/null +++ b/v3/internal/generator/testdata/package.json @@ -0,0 +1,10 @@ +{ + "name": "testdata", + "version": "0.0.0", + "description": "Output from generator testcases. This package.json is here only to pull in the Typescript compiler as a dependency.", + "type": "module", + "devDependencies": { + "typescript": "^5.7.3", + "madge": "^8.0.0" + } +} diff --git a/v3/internal/generator/testdata/tsconfig.json b/v3/internal/generator/testdata/tsconfig.json new file mode 100644 index 000000000..aa4939e6a --- /dev/null +++ b/v3/internal/generator/testdata/tsconfig.json @@ -0,0 +1,35 @@ +{ + "include": ["output/**/*.js", "output/**/*.ts"], + "exclude": ["output/**/*.got.?s"], + "references": [ + { "path": "../../runtime/desktop/@wailsio/runtime" } + ], + "compilerOptions": { + "allowJs": true, + + "noEmit": true, + "skipLibCheck": true, + + "target": "ES2015", + "module": "ES2015", + "moduleResolution": "bundler", + "isolatedModules": true, + "verbatimModuleSyntax": true, + + "lib": [ + "DOM", + "ESNext" + ], + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "/wails/runtime.js": ["../../runtime/desktop/@wailsio/runtime/src/index.ts"] + } + } +} diff --git a/v3/internal/github/github.go b/v3/internal/github/github.go new file mode 100644 index 000000000..da534177a --- /dev/null +++ b/v3/internal/github/github.go @@ -0,0 +1,142 @@ +package github + +import ( + "encoding/json" + "fmt" + "github.com/charmbracelet/glamour/styles" + "io" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/charmbracelet/glamour" +) + +func GetReleaseNotes(tagVersion string, noColour bool) string { + resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/releases/tags/" + url.PathEscape(tagVersion)) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + + data := map[string]interface{}{} + err = json.Unmarshal(body, &data) + if err != nil { + return "Unable to retrieve release notes. Please check your network connection" + } + + if data["body"] == nil { + return "No release notes found" + } + + result := "# Release Notes for " + tagVersion + "\n" + data["body"].(string) + var renderer *glamour.TermRenderer + + if noColour { + renderer, err = glamour.NewTermRenderer(glamour.WithStyles(styles.NoTTYStyleConfig)) + } else { + renderer, err = glamour.NewTermRenderer(glamour.WithAutoStyle()) + } + if err != nil { + return result + } + result, err = renderer.Render(result) + if err != nil { + return err.Error() + } + return result +} + +// GetVersionTags gets the list of tags on the Wails repo +// It returns a list of sorted tags in descending order +func GetVersionTags() ([]*SemanticVersion, error) { + result := []*SemanticVersion{} + var err error + + resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags") + if err != nil { + return result, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + data := []map[string]interface{}{} + err = json.Unmarshal(body, &data) + if err != nil { + return result, err + } + + // Convert tag data to Version structs + for _, tag := range data { + version := tag["name"].(string) + if !strings.HasPrefix(version, "v2") { + continue + } + semver, err := NewSemanticVersion(version) + if err != nil { + return result, err + } + result = append(result, semver) + } + + // Reverse Sort + sort.Sort(sort.Reverse(SemverCollection(result))) + + return result, err +} + +// GetLatestStableRelease gets the latest stable release on GitHub +func GetLatestStableRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() + if err != nil { + return nil, err + } + + for _, tag := range tags { + if tag.IsRelease() { + return tag, nil + } + } + + return nil, fmt.Errorf("no release tag found") +} + +// GetLatestPreRelease gets the latest prerelease on GitHub +func GetLatestPreRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() + if err != nil { + return nil, err + } + + for _, tag := range tags { + if tag.IsPreRelease() { + return tag, nil + } + } + + return nil, fmt.Errorf("no prerelease tag found") +} + +// IsValidTag returns true if the given string is a valid tag +func IsValidTag(tagVersion string) (bool, error) { + if tagVersion[0] == 'v' { + tagVersion = tagVersion[1:] + } + tags, err := GetVersionTags() + if err != nil { + return false, err + } + + for _, tag := range tags { + if tag.String() == tagVersion { + return true, nil + } + } + return false, nil +} diff --git a/v3/internal/github/semver.go b/v3/internal/github/semver.go new file mode 100644 index 000000000..9062f8820 --- /dev/null +++ b/v3/internal/github/semver.go @@ -0,0 +1,106 @@ +package github + +import ( + "fmt" + + "github.com/Masterminds/semver" +) + +const majorVersion = 3 + +// SemanticVersion is a struct containing a semantic version +type SemanticVersion struct { + Version *semver.Version +} + +// NewSemanticVersion creates a new SemanticVersion object with the given version string +func NewSemanticVersion(version string) (*SemanticVersion, error) { + semverVersion, err := semver.NewVersion(version) + if err != nil { + return nil, err + } + return &SemanticVersion{ + Version: semverVersion, + }, nil +} + +// IsRelease returns true if it's a release version +func (s *SemanticVersion) IsRelease() bool { + if s.Version.Major() != majorVersion { + return false + } + return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0 +} + +// IsPreRelease returns true if it's a prerelease version +func (s *SemanticVersion) IsPreRelease() bool { + if s.Version.Major() != majorVersion { + return false + } + return len(s.Version.Prerelease()) > 0 +} + +func (s *SemanticVersion) String() string { + return s.Version.String() +} + +// IsGreaterThan returns true if this version is greater than the given version +func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) { + // Set up new constraint + constraint, err := semver.NewConstraint("> " + version.Version.String()) + if err != nil { + return false, err + } + + // Check if the desired one is greater than the requested on + success, msgs := constraint.Validate(s.Version) + if !success { + return false, msgs[0] + } + return true, nil +} + +// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version +func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) { + // Set up new constraint + constraint, err := semver.NewConstraint(">= " + version.Version.String()) + if err != nil { + return false, err + } + + // Check if the desired one is greater than the requested on + success, msgs := constraint.Validate(s.Version) + if !success { + return false, msgs[0] + } + return true, nil +} + +// MainVersion returns the main version of any version+prerelease+metadata +// EG: MainVersion("1.2.3-pre") => "1.2.3" +func (s *SemanticVersion) MainVersion() *SemanticVersion { + mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch()) + result, _ := NewSemanticVersion(mainVersion) + return result +} + +// SemverCollection is a collection of SemanticVersion objects +type SemverCollection []*SemanticVersion + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c SemverCollection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c SemverCollection) Less(i, j int) bool { + return c[i].Version.LessThan(c[j].Version) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c SemverCollection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/v3/internal/github/semver_test.go b/v3/internal/github/semver_test.go new file mode 100644 index 000000000..b45e1b3aa --- /dev/null +++ b/v3/internal/github/semver_test.go @@ -0,0 +1,43 @@ +package github + +import ( + "github.com/matryer/is" + "testing" +) + +func TestSemanticVersion_IsGreaterThan(t *testing.T) { + is2 := is.New(t) + + alpha1, err := NewSemanticVersion("v3.0.0-alpha.1") + is2.NoErr(err) + + beta1, err := NewSemanticVersion("v3.0.0-beta.1") + is2.NoErr(err) + + v2, err := NewSemanticVersion("v3.0.0") + is2.NoErr(err) + + is2.True(alpha1.IsPreRelease()) + is2.True(beta1.IsPreRelease()) + is2.True(!v2.IsPreRelease()) + is2.True(v2.IsRelease()) + + result, err := beta1.IsGreaterThan(alpha1) + is2.NoErr(err) + is2.True(result) + + result, err = v2.IsGreaterThan(beta1) + is2.NoErr(err) + is2.True(result) + + beta44, err := NewSemanticVersion("v2.0.0-beta.44.2") + is2.NoErr(err) + + rc1, err := NewSemanticVersion("v2.0.0-rc.1") + is2.NoErr(err) + + result, err = rc1.IsGreaterThan(beta44) + is2.NoErr(err) + is2.True(result) + +} diff --git a/v3/internal/go-common-file-dialog/LICENSE b/v3/internal/go-common-file-dialog/LICENSE new file mode 100644 index 000000000..508b6978e --- /dev/null +++ b/v3/internal/go-common-file-dialog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Harry Phillips + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v3/internal/go-common-file-dialog/README.md b/v3/internal/go-common-file-dialog/README.md new file mode 100644 index 000000000..1cb5902d1 --- /dev/null +++ b/v3/internal/go-common-file-dialog/README.md @@ -0,0 +1,31 @@ +# Common File Dialog bindings for Golang + +[Project Home](https://github.com/harry1453/go-common-file-dialog) + +This library contains bindings for Windows Vista and +newer's [Common File Dialogs](https://docs.microsoft.com/en-us/windows/win32/shell/common-file-dialog), which is the +standard system dialog for selecting files or folders to open or save. + +The Common File Dialogs have to be accessed via +the [COM Interface](https://en.wikipedia.org/wiki/Component_Object_Model), normally via C++ or via bindings (like in C#) +. + +This library contains bindings for Golang. **It does not require CGO**, and contains empty stubs for non-windows +platforms (so is safe to compile and run on platforms other than windows, but will just return errors at runtime). + +This can be very useful if you want to quickly get a file selector in your Golang application. The `cfdutil` package +contains utility functions with a single call to open and configure a dialog, and then get the result from it. Examples +for this are in [`_examples/usingutil`](_examples/usingutil). Or, if you want finer control over the dialog's operation, +you can use the base package. Examples for this are in [`_examples/notusingutil`](_examples/notusingutil). + +This library is available under the MIT license. + +Currently supported features: + +* Open File Dialog (to open a single file) +* Open Multiple Files Dialog (to open multiple files) +* Open Folder Dialog +* Save File Dialog +* Dialog "roles" to allow Windows to remember different "last locations" for different types of dialog +* Set dialog Title, Default Folder and Initial Folder +* Set dialog File Filters diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go new file mode 100644 index 000000000..58e97aa4e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog.go @@ -0,0 +1,72 @@ +// Cross-platform. + +// Common File Dialogs +package cfd + +type Dialog interface { + // Show the dialog to the user. + // Blocks until the user has closed the dialog. + Show() error + // Sets the dialog's parent window. Use 0 to set the dialog to have no parent window. + SetParentWindowHandle(hwnd uintptr) + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns their selection. + // Returns an error if the user cancelled the dialog. + // Do not use for the Open Multiple Files dialog. Use ShowAndGetResults instead. + ShowAndGetResult() (string, error) + // Sets the title of the dialog window. + SetTitle(title string) error + // Sets the "role" of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + SetRole(role string) error + // Sets the folder used as a default if there is not a recently used folder value available + SetDefaultFolder(defaultFolder string) error + // Sets the folder that the dialog always opens to. + // If this is set, it will override the "default folder" behaviour and the dialog will always open to this folder. + SetFolder(folder string) error + // Gets the selected file or folder path, as an absolute path eg. "C:\Folder\file.txt" + // Do not use for the Open Multiple Files dialog. Use GetResults instead. + GetResult() (string, error) + // Sets the file name, I.E. the contents of the file name text box. + // For Select Folder Dialog, sets folder name. + SetFileName(fileName string) error + // Release the resources allocated to this Dialog. + // Should be called when the dialog is finished with. + Release() error +} + +type FileDialog interface { + Dialog + // Set the list of file filters that the user can select. + SetFileFilters(fileFilter []FileFilter) error + // Set the selected item from the list of file filters (set using SetFileFilters) by its index. Defaults to 0 (the first item in the list) if not called. + SetSelectedFileFilterIndex(index uint) error + // Sets the default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + SetDefaultExtension(defaultExtension string) error +} + +type OpenFileDialog interface { + FileDialog +} + +type OpenMultipleFilesDialog interface { + FileDialog + // Show the dialog to the user. + // Blocks until the user has closed the dialog and returns the selected files. + ShowAndGetResults() ([]string, error) + // Gets the selected file paths, as absolute paths eg. "C:\Folder\file.txt" + GetResults() ([]string, error) +} + +type SelectFolderDialog interface { + Dialog +} + +type SaveFileDialog interface { // TODO Properties + FileDialog +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go new file mode 100644 index 000000000..3ab969850 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go @@ -0,0 +1,28 @@ +//go:build !windows +// +build !windows + +package cfd + +import "fmt" + +var unsupportedError = fmt.Errorf("common file dialogs are only available on windows") + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + return nil, unsupportedError +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + return nil, unsupportedError +} diff --git a/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go new file mode 100644 index 000000000..69f46118e --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/CommonFileDialog_windows.go @@ -0,0 +1,79 @@ +//go:build windows +// +build windows + +package cfd + +import "github.com/go-ole/go-ole" + +func initialize() { + // Swallow error + _ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE) +} + +// TODO doc +func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setIsMultiselect(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { + initialize() + + openDialog, err := newIFileOpenDialog() + if err != nil { + return nil, err + } + err = config.apply(openDialog) + if err != nil { + return nil, err + } + err = openDialog.setPickFolders(true) + if err != nil { + return nil, err + } + return openDialog, nil +} + +// TODO doc +func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { + initialize() + + saveDialog, err := newIFileSaveDialog() + if err != nil { + return nil, err + } + err = config.apply(saveDialog) + if err != nil { + return nil, err + } + return saveDialog, nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/DialogConfig.go b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go new file mode 100644 index 000000000..800573ed2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/DialogConfig.go @@ -0,0 +1,141 @@ +// Cross-platform. + +package cfd + +import ( + "reflect" + "fmt" + "os" +) + +type FileFilter struct { + // The display name of the filter (That is shown to the user) + DisplayName string + // The filter pattern. Eg. "*.txt;*.png" to select all txt and png files, "*.*" to select any files, etc. + Pattern string +} + +// Never obfuscate the FileFilter type. +var _ = reflect.TypeOf(FileFilter{}) + +type DialogConfig struct { + // The title of the dialog + Title string + // The role of the dialog. This is used to derive the dialog's GUID, which the + // OS will use to differentiate it from dialogs that are intended for other purposes. + // This means that, for example, a dialog with role "Import" will have a different + // previous location that it will open to than a dialog with role "Open". Can be any string. + Role string + // The default folder - the folder that is used the first time the user opens it + // (after the first time their last used location is used). + DefaultFolder string + // The initial folder - the folder that the dialog always opens to if not empty. + // If this is not empty, it will override the "default folder" behaviour and + // the dialog will always open to this folder. + Folder string + // The file filters that restrict which types of files the dialog is able to choose. + // Ignored by Select Folder Dialog. + FileFilters []FileFilter + // Sets the initially selected file filter. This is an index of FileFilters. + // Ignored by Select Folder Dialog. + SelectedFileFilterIndex uint + // The initial name of the file (I.E. the text in the file name text box) when the user opens the dialog. + // For the Select Folder Dialog, this sets the initial folder name. + FileName string + // The default extension applied when a user does not provide one as part of the file name. + // If the user selects a different file filter, the default extension will be automatically updated to match the new file filter. + // For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists. + // For Save File Dialog, this extension will be used whenever a user does not specify an extension. + // Ignored by Select Folder Dialog. + DefaultExtension string + // ParentWindowHandle is the handle (HWND) to the parent window of the dialog. + // If left as 0 / nil, the dialog will have no parent window. + ParentWindowHandle uintptr +} + +var defaultFilters = []FileFilter{ + { + DisplayName: "All Files (*.*)", + Pattern: "*.*", + }, +} + +func (config *DialogConfig) apply(dialog Dialog) (err error) { + if config.Title != "" { + err = dialog.SetTitle(config.Title) + if err != nil { + return + } + } + + if config.Role != "" { + err = dialog.SetRole(config.Role) + if err != nil { + return + } + } + + if config.Folder != "" { + _, err = os.Stat(config.Folder) + if err != nil { + return + } + err = dialog.SetFolder(config.Folder) + if err != nil { + return + } + } + + if config.DefaultFolder != "" { + _, err = os.Stat(config.DefaultFolder) + if err != nil { + return + } + err = dialog.SetDefaultFolder(config.DefaultFolder) + if err != nil { + return + } + } + + if config.FileName != "" { + err = dialog.SetFileName(config.FileName) + if err != nil { + return + } + } + + dialog.SetParentWindowHandle(config.ParentWindowHandle) + + if dialog, ok := dialog.(FileDialog); ok { + var fileFilters []FileFilter + if config.FileFilters != nil && len(config.FileFilters) > 0 { + fileFilters = config.FileFilters + } else { + fileFilters = defaultFilters + } + err = dialog.SetFileFilters(fileFilters) + if err != nil { + return + } + + if config.SelectedFileFilterIndex != 0 { + if config.SelectedFileFilterIndex > uint(len(fileFilters)) { + err = fmt.Errorf("selected file filter index out of range") + return + } + err = dialog.SetSelectedFileFilterIndex(config.SelectedFileFilterIndex) + if err != nil { + return + } + } + + if config.DefaultExtension != "" { + err = dialog.SetDefaultExtension(config.DefaultExtension) + if err != nil { + return + } + } + } + + return +} diff --git a/v3/internal/go-common-file-dialog/cfd/errors.go b/v3/internal/go-common-file-dialog/cfd/errors.go new file mode 100644 index 000000000..c097c8eb2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/errors.go @@ -0,0 +1,7 @@ +package cfd + +import "errors" + +var ( + ErrorCancelled = errors.New("cancelled by user") +) diff --git a/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go new file mode 100644 index 000000000..404dedc22 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go @@ -0,0 +1,200 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" + "syscall" + "unsafe" +) + +var ( + fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") + fileOpenDialogIID = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}") +) + +type iFileOpenDialog struct { + vtbl *iFileOpenDialogVtbl + parentWindowHandle uintptr +} + +type iFileOpenDialogVtbl struct { + iFileDialogVtbl + + GetResults uintptr // func (ppenum **IShellItemArray) HRESULT + GetSelectedItems uintptr +} + +func newIFileOpenDialog() (*iFileOpenDialog, error) { + if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil { + return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileOpenDialog *iFileOpenDialog) Show() error { + return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle) +} + +func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) { + fileOpenDialog.parentWindowHandle = hwnd +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResults for open multiple files dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return "", err + } + return fileOpenDialog.GetResult() +} + +func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use ShowAndGetResult for open single file dialog") + } + if err := fileOpenDialog.Show(); err != nil { + return nil, err + } + return fileOpenDialog.GetResults() +} + +func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error { + return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title) +} + +func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return "", err + } + if isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResults for open multiple files dialog") + } + return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) Release() error { + return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error { + return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error { + return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter) +} + +func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error { + return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), StringToUUID(role)) +} + +// This should only be callable when the user asks for a multi select because +// otherwise they will be given the Dialog interface which does not expose this function. +func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) { + isMultiselect, err := fileOpenDialog.isMultiselect() + if err != nil { + return nil, err + } + if !isMultiselect { + // We should panic as this error is caused by the developer using the library + panic("use GetResult for open single file dialog") + } + return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog)) +} + +func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error { + return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension) +} + +func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error { + return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName) +} + +func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error { + return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index) +} + +func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error { + const FosPickfolders = 0x20 + if pickFolders { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders) + } +} + +const FosAllowMultiselect = 0x200 + +func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) { + options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog)) + if err != nil { + return false, err + } + return options&FosAllowMultiselect != 0, nil +} + +func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error { + if isMultiselect { + return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } else { + return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect) + } +} + +func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) { + var shellItemArray *iShellItemArray + ret, _, _ := syscall.SyscallN(vtbl.GetResults, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItemArray)), + 0) + return shellItemArray, hresultToError(ret) +} + +func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) { + shellItemArray, err := vtbl.getResults(objPtr) + if err != nil { + return nil, err + } + if shellItemArray == nil { + return nil, ErrorCancelled + } + defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray)) + count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray)) + if err != nil { + return nil, err + } + var results []string + for i := uintptr(0); i < count; i++ { + newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i) + if err != nil { + return nil, err + } + results = append(results, newItem) + } + return results, nil +} + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go new file mode 100644 index 000000000..ddee7b246 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iFileSaveDialog.go @@ -0,0 +1,92 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "unsafe" +) + +var ( + saveFileDialogCLSID = ole.NewGUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + saveFileDialogIID = ole.NewGUID("{84bccd23-5fde-4cdb-aea4-af64b83d78ab}") +) + +type iFileSaveDialog struct { + vtbl *iFileSaveDialogVtbl + parentWindowHandle uintptr +} + +type iFileSaveDialogVtbl struct { + iFileDialogVtbl + + SetSaveAsItem uintptr + SetProperties uintptr + SetCollectedProperties uintptr + GetProperties uintptr + ApplyProperties uintptr +} + +func newIFileSaveDialog() (*iFileSaveDialog, error) { + if unknown, err := ole.CreateInstance(saveFileDialogCLSID, saveFileDialogIID); err == nil { + return (*iFileSaveDialog)(unsafe.Pointer(unknown)), nil + } else { + return nil, err + } +} + +func (fileSaveDialog *iFileSaveDialog) Show() error { + return fileSaveDialog.vtbl.show(unsafe.Pointer(fileSaveDialog), fileSaveDialog.parentWindowHandle) +} + +func (fileSaveDialog *iFileSaveDialog) SetParentWindowHandle(hwnd uintptr) { + fileSaveDialog.parentWindowHandle = hwnd +} + +func (fileSaveDialog *iFileSaveDialog) ShowAndGetResult() (string, error) { + if err := fileSaveDialog.Show(); err != nil { + return "", err + } + return fileSaveDialog.GetResult() +} + +func (fileSaveDialog *iFileSaveDialog) SetTitle(title string) error { + return fileSaveDialog.vtbl.setTitle(unsafe.Pointer(fileSaveDialog), title) +} + +func (fileSaveDialog *iFileSaveDialog) GetResult() (string, error) { + return fileSaveDialog.vtbl.getResultString(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) Release() error { + return fileSaveDialog.vtbl.release(unsafe.Pointer(fileSaveDialog)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFolder(defaultFolderPath string) error { + return fileSaveDialog.vtbl.setFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error { + return fileSaveDialog.vtbl.setFileTypes(unsafe.Pointer(fileSaveDialog), filter) +} + +func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error { + return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), StringToUUID(role)) +} + +func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error { + return fileSaveDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileSaveDialog), defaultExtension) +} + +func (fileSaveDialog *iFileSaveDialog) SetFileName(initialFileName string) error { + return fileSaveDialog.vtbl.setFileName(unsafe.Pointer(fileSaveDialog), initialFileName) +} + +func (fileSaveDialog *iFileSaveDialog) SetSelectedFileFilterIndex(index uint) error { + return fileSaveDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileSaveDialog), index) +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItem.go b/v3/internal/go-common-file-dialog/cfd/iShellItem.go new file mode 100644 index 000000000..080115345 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItem.go @@ -0,0 +1,56 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +var ( + procSHCreateItemFromParsingName = syscall.NewLazyDLL("Shell32.dll").NewProc("SHCreateItemFromParsingName") + iidShellItem = ole.NewGUID("43826d1e-e718-42ee-bc55-a1e261c37bfe") +) + +type iShellItem struct { + vtbl *iShellItemVtbl +} + +type iShellItemVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetParent uintptr + GetDisplayName uintptr // func (sigdnName SIGDN, ppszName *LPWSTR) HRESULT + GetAttributes uintptr + Compare uintptr +} + +func newIShellItem(path string) (*iShellItem, error) { + var shellItem *iShellItem + pathPtr := ole.SysAllocString(path) + defer func(v *int16) { + _ = ole.SysFreeString(v) + }(pathPtr) + + ret, _, _ := procSHCreateItemFromParsingName.Call( + uintptr(unsafe.Pointer(pathPtr)), + 0, + uintptr(unsafe.Pointer(iidShellItem)), + uintptr(unsafe.Pointer(&shellItem))) + return shellItem, hresultToError(ret) +} + +func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) { + var ptr *uint16 + ret, _, _ := syscall.SyscallN(vtbl.GetDisplayName, + uintptr(objPtr), + 0x80058000, // SIGDN_FILESYSPATH, + uintptr(unsafe.Pointer(&ptr))) + if err := hresultToError(ret); err != nil { + return "", err + } + defer ole.CoTaskMemFree(uintptr(unsafe.Pointer(ptr))) + return ole.LpOleStrToString(ptr), nil +} diff --git a/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go new file mode 100644 index 000000000..bdd459402 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/iShellItemArray.go @@ -0,0 +1,65 @@ +//go:build windows +// +build windows + +package cfd + +import ( + "fmt" + "github.com/go-ole/go-ole" + "syscall" + "unsafe" +) + +const ( + iidShellItemArrayGUID = "{b63ea76d-1f85-456f-a19c-48159efa858b}" +) + +var ( + iidShellItemArray *ole.GUID +) + +func init() { + iidShellItemArray, _ = ole.IIDFromString(iidShellItemArrayGUID) +} + +type iShellItemArray struct { + vtbl *iShellItemArrayVtbl +} + +type iShellItemArrayVtbl struct { + iUnknownVtbl + BindToHandler uintptr + GetPropertyStore uintptr + GetPropertyDescriptionList uintptr + GetAttributes uintptr + GetCount uintptr // func (pdwNumItems *DWORD) HRESULT + GetItemAt uintptr // func (dwIndex DWORD, ppsi **IShellItem) HRESULT + EnumItems uintptr +} + +func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error) { + var count uintptr + ret, _, _ := syscall.SyscallN(vtbl.GetCount, + uintptr(objPtr), + uintptr(unsafe.Pointer(&count))) + if err := hresultToError(ret); err != nil { + return 0, err + } + return count, nil +} + +func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) (string, error) { + var shellItem *iShellItem + ret, _, _ := syscall.SyscallN(vtbl.GetItemAt, + uintptr(objPtr), + index, + uintptr(unsafe.Pointer(&shellItem))) + if err := hresultToError(ret); err != nil { + return "", err + } + if shellItem == nil { + return "", fmt.Errorf("shellItem is nil") + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommon.go b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go new file mode 100644 index 000000000..21015c27c --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommon.go @@ -0,0 +1,48 @@ +//go:build windows +// +build windows + +package cfd + +type comDlgFilterSpec struct { + pszName *int16 + pszSpec *int16 +} + +type iUnknownVtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr +} + +type iModalWindowVtbl struct { + iUnknownVtbl + Show uintptr // func (hwndOwner HWND) HRESULT +} + +type iFileDialogVtbl struct { + iModalWindowVtbl + SetFileTypes uintptr // func (cFileTypes UINT, rgFilterSpec *COMDLG_FILTERSPEC) HRESULT + SetFileTypeIndex uintptr // func(iFileType UINT) HRESULT + GetFileTypeIndex uintptr + Advise uintptr + Unadvise uintptr + SetOptions uintptr // func (fos FILEOPENDIALOGOPTIONS) HRESULT + GetOptions uintptr // func (pfos *FILEOPENDIALOGOPTIONS) HRESULT + SetDefaultFolder uintptr // func (psi *IShellItem) HRESULT + SetFolder uintptr // func (psi *IShellItem) HRESULT + GetFolder uintptr + GetCurrentSelection uintptr + SetFileName uintptr // func (pszName LPCWSTR) HRESULT + GetFileName uintptr + SetTitle uintptr // func(pszTitle LPCWSTR) HRESULT + SetOkButtonLabel uintptr + SetFileNameLabel uintptr + GetResult uintptr // func (ppsi **IShellItem) HRESULT + AddPlace uintptr + SetDefaultExtension uintptr // func (pszDefaultExtension LPCWSTR) HRESULT + // This can only be used from a callback. + Close uintptr + SetClientGuid uintptr // func (guid REFGUID) HRESULT + ClearClientData uintptr + SetFilter uintptr +} diff --git a/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go new file mode 100644 index 000000000..9107c1904 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfd/vtblCommonFunc.go @@ -0,0 +1,225 @@ +//go:build windows + +package cfd + +import ( + "fmt" + "github.com/go-ole/go-ole" + "strings" + "syscall" + "unsafe" +) + +func hresultToError(hr uintptr) error { + if hr < 0 { + return ole.NewError(hr) + } + return nil +} + +func (vtbl *iUnknownVtbl) release(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.SyscallN(vtbl.Release, + uintptr(objPtr), + 0) + return hresultToError(ret) +} + +func (vtbl *iModalWindowVtbl) show(objPtr unsafe.Pointer, hwnd uintptr) error { + ret, _, _ := syscall.SyscallN(vtbl.Show, + uintptr(objPtr), + hwnd) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileFilter) error { + cFileTypes := len(filters) + if cFileTypes < 0 { + return fmt.Errorf("must specify at least one filter") + } + comDlgFilterSpecs := make([]comDlgFilterSpec, cFileTypes) + for i := 0; i < cFileTypes; i++ { + filter := &filters[i] + comDlgFilterSpecs[i] = comDlgFilterSpec{ + pszName: ole.SysAllocString(filter.DisplayName), + pszSpec: ole.SysAllocString(filter.Pattern), + } + } + + // Ensure memory is freed after use + defer func() { + for _, spec := range comDlgFilterSpecs { + ole.SysFreeString(spec.pszName) + ole.SysFreeString(spec.pszSpec) + } + }() + + ret, _, _ := syscall.SyscallN(vtbl.SetFileTypes, + uintptr(objPtr), + uintptr(cFileTypes), + uintptr(unsafe.Pointer(&comDlgFilterSpecs[0]))) + return hresultToError(ret) +} + +// Options are: +// FOS_OVERWRITEPROMPT = 0x2, +// FOS_STRICTFILETYPES = 0x4, +// FOS_NOCHANGEDIR = 0x8, +// FOS_PICKFOLDERS = 0x20, +// FOS_FORCEFILESYSTEM = 0x40, +// FOS_ALLNONSTORAGEITEMS = 0x80, +// FOS_NOVALIDATE = 0x100, +// FOS_ALLOWMULTISELECT = 0x200, +// FOS_PATHMUSTEXIST = 0x800, +// FOS_FILEMUSTEXIST = 0x1000, +// FOS_CREATEPROMPT = 0x2000, +// FOS_SHAREAWARE = 0x4000, +// FOS_NOREADONLYRETURN = 0x8000, +// FOS_NOTESTFILECREATE = 0x10000, +// FOS_HIDEMRUPLACES = 0x20000, +// FOS_HIDEPINNEDPLACES = 0x40000, +// FOS_NODEREFERENCELINKS = 0x100000, +// FOS_OKBUTTONNEEDSINTERACTION = 0x200000, +// FOS_DONTADDTORECENT = 0x2000000, +// FOS_FORCESHOWHIDDEN = 0x10000000, +// FOS_DEFAULTNOMINIMODE = 0x20000000, +// FOS_FORCEPREVIEWPANEON = 0x40000000, +// FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +func (vtbl *iFileDialogVtbl) setOptions(objPtr unsafe.Pointer, options uint32) error { + ret, _, _ := syscall.SyscallN(vtbl.SetOptions, + uintptr(objPtr), + uintptr(options)) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getOptions(objPtr unsafe.Pointer) (uint32, error) { + var options uint32 + ret, _, _ := syscall.SyscallN(vtbl.GetOptions, + uintptr(objPtr), + uintptr(unsafe.Pointer(&options))) + return options, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) addOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options|option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) removeOption(objPtr unsafe.Pointer, option uint32) error { + if options, err := vtbl.getOptions(objPtr); err == nil { + return vtbl.setOptions(objPtr, options&^option) + } else { + return err + } +} + +func (vtbl *iFileDialogVtbl) setDefaultFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.SyscallN(vtbl.SetDefaultFolder, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFolder(objPtr unsafe.Pointer, path string) error { + shellItem, err := newIShellItem(path) + if err != nil { + return err + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + ret, _, _ := syscall.SyscallN(vtbl.SetFolder, + uintptr(objPtr), + uintptr(unsafe.Pointer(shellItem))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setTitle(objPtr unsafe.Pointer, title string) error { + titlePtr := ole.SysAllocString(title) + defer ole.SysFreeString(titlePtr) // Ensure the string is freed + ret, _, _ := syscall.SyscallN(vtbl.SetTitle, + uintptr(objPtr), + uintptr(unsafe.Pointer(titlePtr))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) close(objPtr unsafe.Pointer) error { + ret, _, _ := syscall.SyscallN(vtbl.Close, + uintptr(objPtr)) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResult(objPtr unsafe.Pointer) (*iShellItem, error) { + var shellItem *iShellItem + ret, _, _ := syscall.SyscallN(vtbl.GetResult, + uintptr(objPtr), + uintptr(unsafe.Pointer(&shellItem))) + return shellItem, hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, error) { + shellItem, err := vtbl.getResult(objPtr) + if err != nil { + return "", err + } + if shellItem == nil { + return "", fmt.Errorf("shellItem is nil") + } + defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) + return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) +} + +func (vtbl *iFileDialogVtbl) setClientGuid(objPtr unsafe.Pointer, guid *ole.GUID) error { + // Ensure the GUID is not nil + if guid == nil { + return fmt.Errorf("guid cannot be nil") + } + + // Call the SetClientGuid method + ret, _, _ := syscall.SyscallN(vtbl.SetClientGuid, + uintptr(objPtr), + uintptr(unsafe.Pointer(guid))) + + // Convert the HRESULT to a Go error + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setDefaultExtension(objPtr unsafe.Pointer, defaultExtension string) error { + // Ensure the string is not empty before accessing the first character + if len(defaultExtension) > 0 && defaultExtension[0] == '.' { + defaultExtension = strings.TrimPrefix(defaultExtension, ".") + } + + // Allocate memory for the default extension string + defaultExtensionPtr := ole.SysAllocString(defaultExtension) + defer ole.SysFreeString(defaultExtensionPtr) // Ensure the string is freed + + // Call the SetDefaultExtension method + ret, _, _ := syscall.SyscallN(vtbl.SetDefaultExtension, + uintptr(objPtr), + uintptr(unsafe.Pointer(defaultExtensionPtr))) + + // Convert the HRESULT to a Go error + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setFileName(objPtr unsafe.Pointer, fileName string) error { + fileNamePtr := ole.SysAllocString(fileName) + defer ole.SysFreeString(fileNamePtr) // Ensure the string is freed + ret, _, _ := syscall.SyscallN(vtbl.SetFileName, + uintptr(objPtr), + uintptr(unsafe.Pointer(fileNamePtr))) + return hresultToError(ret) +} + +func (vtbl *iFileDialogVtbl) setSelectedFileFilterIndex(objPtr unsafe.Pointer, index uint) error { + ret, _, _ := syscall.SyscallN(vtbl.SetFileTypeIndex, + uintptr(objPtr), + uintptr(index+1)) // SetFileTypeIndex counts from 1 + return hresultToError(ret) +} diff --git a/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go new file mode 100644 index 000000000..aa3a783b2 --- /dev/null +++ b/v3/internal/go-common-file-dialog/cfdutil/CFDUtil.go @@ -0,0 +1,45 @@ +package cfdutil + +import ( + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" +) + +// TODO doc +func ShowOpenFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewOpenFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowOpenMultipleFilesDialog(config cfd.DialogConfig) ([]string, error) { + dialog, err := cfd.NewOpenMultipleFilesDialog(config) + if err != nil { + return nil, err + } + defer dialog.Release() + return dialog.ShowAndGetResults() +} + +// TODO doc +func ShowPickFolderDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSelectFolderDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} + +// TODO doc +func ShowSaveFileDialog(config cfd.DialogConfig) (string, error) { + dialog, err := cfd.NewSaveFileDialog(config) + if err != nil { + return "", err + } + defer dialog.Release() + return dialog.ShowAndGetResult() +} diff --git a/v3/internal/go-common-file-dialog/util/util.go b/v3/internal/go-common-file-dialog/util/util.go new file mode 100644 index 000000000..723fbedc0 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util.go @@ -0,0 +1,10 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "github.com/google/uuid" +) + +func StringToUUID(str string) *ole.GUID { + return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) +} diff --git a/v3/internal/go-common-file-dialog/util/util_test.go b/v3/internal/go-common-file-dialog/util/util_test.go new file mode 100644 index 000000000..2e8ffeb05 --- /dev/null +++ b/v3/internal/go-common-file-dialog/util/util_test.go @@ -0,0 +1,14 @@ +package util + +import ( + "github.com/go-ole/go-ole" + "testing" +) + +func TestStringToUUID(t *testing.T) { + generated := *StringToUUID("TestTestTest") + expected := *ole.NewGUID("7933985F-2C87-5A5B-A26E-5D0326829AC2") + if generated != expected { + t.Errorf("not equal. expected %s, found %s", expected.String(), generated.String()) + } +} diff --git a/v3/internal/hash/fnv.go b/v3/internal/hash/fnv.go new file mode 100644 index 000000000..bc18ee817 --- /dev/null +++ b/v3/internal/hash/fnv.go @@ -0,0 +1,9 @@ +package hash + +import "hash/fnv" + +func Fnv(s string) uint32 { + h := fnv.New32a() + _, _ = h.Write([]byte(s)) // Hash implementations never return errors (see https://pkg.go.dev/hash#Hash) + return h.Sum32() +} diff --git a/v3/internal/operatingsystem/os.go b/v3/internal/operatingsystem/os.go new file mode 100644 index 000000000..2d5656281 --- /dev/null +++ b/v3/internal/operatingsystem/os.go @@ -0,0 +1,23 @@ +package operatingsystem + +// OS contains information about the operating system +type OS struct { + ID string + Name string + Version string + Branding string +} + +func (o *OS) AsLogSlice() []any { + return []any{ + "ID", o.ID, + "Name", o.Name, + "Version", o.Version, + "Branding", o.Branding, + } +} + +// Info retrieves information about the current platform +func Info() (*OS, error) { + return platformInfo() +} diff --git a/v3/internal/operatingsystem/os_darwin.go b/v3/internal/operatingsystem/os_darwin.go new file mode 100644 index 000000000..2975c76f1 --- /dev/null +++ b/v3/internal/operatingsystem/os_darwin.go @@ -0,0 +1,72 @@ +//go:build darwin + +package operatingsystem + +import ( + "os/exec" + "strings" +) + +var macOSNames = map[string]string{ + "10.10": "Yosemite", + "10.11": "El Capitan", + "10.12": "Sierra", + "10.13": "High Sierra", + "10.14": "Mojave", + "10.15": "Catalina", + "11": "Big Sur", + "12": "Monterey", + "13": "Ventura", + "14": "Sonoma", + "15": "Sequoia", + // Add newer versions as they are released... +} + +func getOSName(version string) string { + trimmedVersion := version + if !strings.HasPrefix(version, "10.") { + trimmedVersion = strings.SplitN(version, ".", 2)[0] + } + name, ok := macOSNames[trimmedVersion] + if ok { + return name + } + return "MacOS " + version +} + +func getSysctlValue(key string) (string, error) { + // Run "sysctl" command + command := exec.Command("sysctl", key) + // Capture stdout + var stdout strings.Builder + command.Stdout = &stdout + // Run command + err := command.Run() + if err != nil { + return "", err + } + version := strings.TrimPrefix(stdout.String(), key+": ") + return strings.TrimSpace(version), nil +} + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "MacOS" + result.Version = "Unknown" + + version, err := getSysctlValue("kern.osproductversion") + if err != nil { + return nil, err + } + result.Version = version + ID, err := getSysctlValue("kern.osversion") + if err != nil { + return nil, err + } + result.ID = ID + result.Branding = getOSName(result.Version) + + return &result, nil +} diff --git a/v3/internal/operatingsystem/os_linux.go b/v3/internal/operatingsystem/os_linux.go new file mode 100644 index 000000000..715207dc5 --- /dev/null +++ b/v3/internal/operatingsystem/os_linux.go @@ -0,0 +1,53 @@ +//go:build linux +// +build linux + +package operatingsystem + +import ( + "fmt" + "os" + "strings" +) + +// platformInfo is the platform specific method to get system information +func platformInfo() (*OS, error) { + _, err := os.Stat("/etc/os-release") + if os.IsNotExist(err) { + return nil, fmt.Errorf("unable to read system information") + } + + osRelease, _ := os.ReadFile("/etc/os-release") + return parseOsRelease(string(osRelease)), nil +} + +func parseOsRelease(osRelease string) *OS { + + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Unknown" + result.Version = "Unknown" + + // Split into lines + lines := strings.Split(osRelease, "\n") + // Iterate lines + for _, line := range lines { + // Split each line by the equals char + splitLine := strings.SplitN(line, "=", 2) + // Check we have + if len(splitLine) != 2 { + continue + } + switch splitLine[0] { + case "ID": + result.ID = strings.ToLower(strings.Trim(splitLine[1], `"`)) + case "NAME": + result.Name = strings.Trim(splitLine[1], `"`) + case "VERSION_ID": + result.Version = strings.Trim(splitLine[1], `"`) + case "VERSION": + result.Branding = strings.Trim(splitLine[1], `"`) + } + } + return &result +} diff --git a/v3/internal/operatingsystem/os_windows.go b/v3/internal/operatingsystem/os_windows.go new file mode 100644 index 000000000..2e8c0775b --- /dev/null +++ b/v3/internal/operatingsystem/os_windows.go @@ -0,0 +1,34 @@ +//go:build windows + +package operatingsystem + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/w32" + + "golang.org/x/sys/windows/registry" +) + +func platformInfo() (*OS, error) { + // Default value + var result OS + result.ID = "Unknown" + result.Name = "Windows" + result.Version = "Unknown" + + // Credit: https://stackoverflow.com/a/33288328 + // Ignore errors as it isn't a showstopper + key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + + productName, _, _ := key.GetStringValue("ProductName") + currentBuild, _, _ := key.GetStringValue("CurrentBuildNumber") + displayVersion, _, _ := key.GetStringValue("DisplayVersion") + releaseId, _, _ := key.GetStringValue("ReleaseId") + + result.Name = productName + result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild) + result.ID = displayVersion + result.Branding = w32.GetBranding() + + return &result, key.Close() +} diff --git a/v3/internal/operatingsystem/version_windows.go b/v3/internal/operatingsystem/version_windows.go new file mode 100644 index 000000000..a8f53d134 --- /dev/null +++ b/v3/internal/operatingsystem/version_windows.go @@ -0,0 +1,62 @@ +//go:build windows + +package operatingsystem + +import ( + "strconv" + + "golang.org/x/sys/windows/registry" +) + +type WindowsVersionInfo struct { + Major int + Minor int + Build int + DisplayVersion string +} + +func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber +} + +func GetWindowsVersionInfo() (*WindowsVersionInfo, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + + return &WindowsVersionInfo{ + Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"), + Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"), + Build: regStringKeyAsInt(key, "CurrentBuildNumber"), + DisplayVersion: regKeyAsString(key, "DisplayVersion"), + }, nil +} + +func regDWORDKeyAsInt(key registry.Key, name string) int { + result, _, err := key.GetIntegerValue(name) + if err != nil { + return -1 + } + return int(result) +} + +func regStringKeyAsInt(key registry.Key, name string) int { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return -1 + } + result, err := strconv.Atoi(resultStr) + if err != nil { + return -1 + } + return result +} + +func regKeyAsString(key registry.Key, name string) string { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return "" + } + return resultStr +} diff --git a/v3/internal/operatingsystem/webkit_linux.go b/v3/internal/operatingsystem/webkit_linux.go new file mode 100644 index 000000000..96fcb6c1a --- /dev/null +++ b/v3/internal/operatingsystem/webkit_linux.go @@ -0,0 +1,42 @@ +//go:build linux + +package operatingsystem + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 +#include +*/ +import "C" +import "fmt" + +type WebkitVersion struct { + Major uint + Minor uint + Micro uint +} + +func GetWebkitVersion() WebkitVersion { + var major, minor, micro C.uint + major = C.webkit_get_major_version() + minor = C.webkit_get_minor_version() + micro = C.webkit_get_micro_version() + return WebkitVersion{ + Major: uint(major), + Minor: uint(minor), + Micro: uint(micro), + } +} + +func (v WebkitVersion) String() string { + return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Micro) +} + +func (v WebkitVersion) IsAtLeast(major int, minor int, micro int) bool { + if v.Major != uint(major) { + return v.Major > uint(major) + } + if v.Minor != uint(minor) { + return v.Minor > uint(minor) + } + return v.Micro >= uint(micro) +} diff --git a/v3/internal/packager/packager.go b/v3/internal/packager/packager.go new file mode 100644 index 000000000..5cf235d72 --- /dev/null +++ b/v3/internal/packager/packager.go @@ -0,0 +1,94 @@ +// Package packager provides a simplified interface for creating Linux packages using nfpm +package packager + +import ( + "fmt" + "io" + "os" + + "github.com/goreleaser/nfpm/v2" + _ "github.com/goreleaser/nfpm/v2/apk" // Register APK packager + _ "github.com/goreleaser/nfpm/v2/arch" // Register Arch Linux packager + _ "github.com/goreleaser/nfpm/v2/deb" // Register DEB packager + _ "github.com/goreleaser/nfpm/v2/ipk" // Register IPK packager + _ "github.com/goreleaser/nfpm/v2/rpm" // Register RPM packager +) + +// PackageType represents supported package formats +type PackageType string + +const ( + // DEB is for Debian/Ubuntu packages + DEB PackageType = "deb" + // RPM is for RedHat/CentOS packages + RPM PackageType = "rpm" + // APK is for Alpine Linux packages + APK PackageType = "apk" + // IPK is for OpenWrt packages + IPK PackageType = "ipk" + // ARCH is for Arch Linux packages + ARCH PackageType = "archlinux" +) + +// CreatePackageFromConfig loads a configuration file and creates a package +func CreatePackageFromConfig(pkgType PackageType, configPath string, output string) error { + // Parse nfpm config + config, err := nfpm.ParseFile(configPath) + if err != nil { + return fmt.Errorf("error parsing config file: %w", err) + } + + // Get info for the specified packager + info, err := config.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager info: %w", err) + } + + // Get the packager + packager, err := nfpm.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager: %w", err) + } + + // Create output file + out, err := os.Create(output) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer out.Close() + + // Create the package + if err := packager.Package(info, out); err != nil { + return fmt.Errorf("error creating package: %w", err) + } + + return nil +} + +// CreatePackageFromConfigWriter loads a configuration file and writes the package to the provided writer +func CreatePackageFromConfigWriter(pkgType PackageType, configPath string, output io.Writer) error { + // Parse nfpm config + config, err := nfpm.ParseFile(configPath) + if err != nil { + return fmt.Errorf("error parsing config file: %w", err) + } + + // Get info for the specified packager + info, err := config.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager info: %w", err) + } + + // Get the packager + packager, err := nfpm.Get(string(pkgType)) + if err != nil { + return fmt.Errorf("error getting packager: %w", err) + } + + // Create the package + if err := packager.Package(info, output); err != nil { + return fmt.Errorf("error creating package: %w", err) + } + + return nil +} diff --git a/v3/internal/packager/packager_test.go b/v3/internal/packager/packager_test.go new file mode 100644 index 000000000..3ed4a4f48 --- /dev/null +++ b/v3/internal/packager/packager_test.go @@ -0,0 +1,100 @@ +package packager + +import ( + "bytes" + "os" + "path/filepath" + "testing" +) + +func TestCreatePackageFromConfig(t *testing.T) { + // Create a temporary file for testing + content := []byte("test content") + tmpfile, err := os.CreateTemp("", "example") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + // Create a temporary config file + configContent := []byte(` +name: test-package +version: v1.0.0 +arch: amd64 +description: Test package +maintainer: Test User +license: MIT +contents: +- src: ` + tmpfile.Name() + ` + dst: /usr/local/bin/test-file +`) + + configFile, err := os.CreateTemp("", "config*.yaml") + if err != nil { + t.Fatal(err) + } + defer os.Remove(configFile.Name()) + + if _, err := configFile.Write(configContent); err != nil { + t.Fatal(err) + } + if err := configFile.Close(); err != nil { + t.Fatal(err) + } + + // Test creating packages for each format + formats := []struct { + pkgType PackageType + ext string + }{ + {DEB, "deb"}, + {RPM, "rpm"}, + {APK, "apk"}, + {IPK, "ipk"}, + {ARCH, "pkg.tar.zst"}, + } + + for _, format := range formats { + t.Run(string(format.pkgType), func(t *testing.T) { + // Test file-based package creation + outputPath := filepath.Join(os.TempDir(), "test-package."+format.ext) + err := CreatePackageFromConfig(format.pkgType, configFile.Name(), outputPath) + if err != nil { + t.Errorf("CreatePackageFromConfig failed for %s: %v", format.pkgType, err) + } + defer os.Remove(outputPath) + + // Verify the file was created + if _, err := os.Stat(outputPath); os.IsNotExist(err) { + t.Errorf("Package file was not created for %s", format.pkgType) + } + + // Test writer-based package creation + var buf bytes.Buffer + err = CreatePackageFromConfigWriter(format.pkgType, configFile.Name(), &buf) + if err != nil { + t.Errorf("CreatePackageFromConfigWriter failed for %s: %v", format.pkgType, err) + } + + // Verify some content was written + if buf.Len() == 0 { + t.Errorf("No content was written for %s", format.pkgType) + } + }) + } + + // Test with invalid config file + t.Run("InvalidConfig", func(t *testing.T) { + err := CreatePackageFromConfig(DEB, "nonexistent.yaml", "output.deb") + if err == nil { + t.Error("Expected error for invalid config, got nil") + } + }) +} diff --git a/v3/internal/runtime/.gitignore b/v3/internal/runtime/.gitignore new file mode 100644 index 000000000..059d2134d --- /dev/null +++ b/v3/internal/runtime/.gitignore @@ -0,0 +1,5 @@ +node_modules +.task +*.tsbuildinfo +desktop/@wailsio/runtime/dist/ +desktop/@wailsio/runtime/types/ diff --git a/v3/internal/runtime/README.md b/v3/internal/runtime/README.md new file mode 100644 index 000000000..c4b43d430 --- /dev/null +++ b/v3/internal/runtime/README.md @@ -0,0 +1,3 @@ +# Runtime + +To rebuild the runtime run `task build` or if you have Wails v3 CLI, you can use `wails3 task build`. diff --git a/v3/internal/runtime/Taskfile.yaml b/v3/internal/runtime/Taskfile.yaml new file mode 100644 index 000000000..47877d4ef --- /dev/null +++ b/v3/internal/runtime/Taskfile.yaml @@ -0,0 +1,56 @@ +# https://taskfile.dev + +version: "3" + +tasks: + install-deps: + internal: true + dir: desktop/@wailsio/runtime + sources: + - package.json + cmds: + - npm install + + check: + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm run check + + test: + dir: desktop/@wailsio/runtime + deps: + - install-deps + cmds: + - npm test + + build:debug: + internal: true + cmds: + - npx esbuild@latest desktop/@wailsio/runtime/src/index.ts --inject:desktop/compiled/main.js --format=esm --target=safari11 --bundle --ignore-annotations --tree-shaking=true --sourcemap=inline --outfile=../assetserver/bundledassets/runtime.debug.js --define:DEBUG=true + + build:production: + internal: true + cmds: + - npx esbuild@latest desktop/@wailsio/runtime/src/index.ts --inject:desktop/compiled/main.js --format=esm --target=safari11 --bundle --ignore-annotations --tree-shaking=true --minify --outfile=../assetserver/bundledassets/runtime.js --define:DEBUG=false --drop:console + + build:all: + internal: true + deps: + - build:debug + - build:production + + cmds: + - cmd: echo "Build Complete." + + build: + deps: + - install-deps + cmds: + - task: build:all + + generate:events: + dir: ../../tasks/events + cmds: + - go run generate.go diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll b/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll new file mode 100644 index 000000000..e2ac6616a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js new file mode 100644 index 000000000..08adce9e8 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js @@ -0,0 +1 @@ +window.hierarchyData = "eJylk1FPwjAUhf/LfS64dnSMvYkaXzAY8cUYQup2Jw1dZ7riC9l/N4VIimGmg6c2bc6937mn3YGpa9tA9s7ZmFAWUUIjzkkaU0LjNF4SMFgqzK2sdQPZDjgbu0WLCiGDO6FzVEp8KHw2dSUbnMkNAoGN1AVkjCcEtkZBBlJbNKXIsbk5LxqubaWAQK5E00AGtikGrsrgqHSXa6kKg9oB82jZEuA8+pfnyEJZ+suyb3EWpBPicNASSGPq9Qs3vdrvtVCr4fWmJyzxIJ6wkGJRb03eA8ITBUC0BNy78Jo+fKO2r8J8og1v6on6Op+whFBKubPvVg/l4GK6LUs04Sy+KnACdOK3vS2KvZ+ZbCxqNPOvwycJJugoEAjDIvo3jotJLsQ4CchNZx8OG/lTekFRuA+2sAZFNX2bT91Jn6C6KoTOKWGdOPdYiq2y1xGdFAmEitO4E+rRhSDz66BOivSOko04cYNbtm37AzBNEV4=" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css new file mode 100644 index 000000000..878117697 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/highlight.css @@ -0,0 +1,64 @@ +:root { + --light-hl-0: #0000FF; + --dark-hl-0: #569CD6; + --light-hl-1: #000000; + --dark-hl-1: #D4D4D4; + --light-hl-2: #001080; + --dark-hl-2: #9CDCFE; + --light-hl-3: #795E26; + --dark-hl-3: #DCDCAA; + --light-hl-4: #008000; + --dark-hl-4: #6A9955; + --light-hl-5: #AF00DB; + --dark-hl-5: #C586C0; + --light-code-background: #FFFFFF; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --code-background: var(--dark-code-background); +} } + +:root[data-theme='light'] { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --code-background: var(--light-code-background); +} + +:root[data-theme='dark'] { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --code-background: var(--dark-code-background); +} + +.hl-0 { color: var(--hl-0); } +.hl-1 { color: var(--hl-1); } +.hl-2 { color: var(--hl-2); } +.hl-3 { color: var(--hl-3); } +.hl-4 { color: var(--hl-4); } +.hl-5 { color: var(--hl-5); } +pre, code { background: var(--code-background); } diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js new file mode 100644 index 000000000..58882d76d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.js @@ -0,0 +1,18 @@ +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } + + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg new file mode 100644 index 000000000..50ad5799d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/icons.svg @@ -0,0 +1 @@ +MMNEPVFCICPMFPCPTTAAATR \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js new file mode 100644 index 000000000..2363f64c2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/main.js @@ -0,0 +1,60 @@ +"use strict"; +window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; +"use strict";(()=>{var De=Object.create;var le=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var Ve=Object.getPrototypeOf,Be=Object.prototype.hasOwnProperty;var qe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var je=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ne(e))!Be.call(t,i)&&i!==n&&le(t,i,{get:()=>e[i],enumerable:!(r=Fe(e,i))||r.enumerable});return t};var $e=(t,e,n)=>(n=t!=null?De(Ve(t)):{},je(e||!t||!t.__esModule?le(n,"default",{value:t,enumerable:!0}):n,t));var pe=qe((de,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[c+1]*i[d+1],c+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof de=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var ce=[];function G(t,e){ce.push({selector:e,constructor:t})}var J=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){ce.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!ze(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function ze(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var ue=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var ge=$e(pe(),1);async function A(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}async function fe(t,e){if(!window.searchData)return;let n=await A(window.searchData);t.data=n,t.index=ge.Index.load(n.index),e.classList.remove("loading"),e.classList.add("ready")}function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:document.documentElement.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{fe(e,t)}),fe(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{re(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),We(t,i,r,e)}function We(t,e,n,r){n.addEventListener("input",ue(()=>{Ue(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Je(e,t):i.key=="ArrowUp"?(me(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(me(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),re(t))})}function re(t){t.classList.remove("has-focus")}function Ue(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=ye(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` + ${ye(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=c+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function me(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Je(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),re(e)}}function ye(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ne(t.substring(s,o)),`${ne(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ne(t.substring(s))),i.join("")}var Ge={"&":"&","<":"<",">":">","'":"'",'"':"""};function ne(t){return t.replace(/[&<>"'"]/g,e=>Ge[e])}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var H="mousedown",Ee="mousemove",B="mouseup",X={x:0,y:0},xe=!1,ie=!1,Xe=!1,D=!1,be=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(be?"is-mobile":"not-mobile");be&&"ontouchstart"in document.documentElement&&(Xe=!0,H="touchstart",Ee="touchmove",B="touchend");document.addEventListener(H,t=>{ie=!0,D=!1;let e=H=="touchstart"?t.targetTouches[0]:t;X.y=e.pageY||0,X.x=e.pageX||0});document.addEventListener(Ee,t=>{if(ie&&!D){let e=H=="touchstart"?t.targetTouches[0]:t,n=X.x-(e.pageX||0),r=X.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var Y=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(H,n=>this.onDocumentPointerDown(n)),document.addEventListener(B,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var se;try{se=localStorage}catch{se={getItem(){return null},setItem(){}}}var C=se;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var Z=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=C.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){C.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var oe=new Map,ae=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;C.setItem(this.key,e.toString())}},K=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(oe.has(i))s=oe.get(i);else{let o=C.getItem(i),a=o?o==="true":this.el.open;s=new ae(i,a),oe.set(i,s)}s.add(this.el)}};function Se(t){let e=C.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{C.setItem("tsd-theme",t.value),we(t.value)})}function we(t){document.documentElement.dataset.theme=t}var ee;function Ce(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Te),Te())}async function Te(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await A(window.navigationData);ee=document.documentElement.dataset.base,ee.endsWith("/")||(ee+="/"),t.innerHTML="";for(let n of e)Ie(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Ie(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',ke(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let c of t.children)Ie(c,l,i)}else ke(t,r,t.class)}function ke(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=ee+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&r.classList.add("current"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(document.createElement("span")).textContent=t.text}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(document.createElement("span")).textContent=t.text}}var te=document.documentElement.dataset.base;te.endsWith("/")||(te+="/");function Pe(){document.querySelector(".tsd-full-hierarchy")?Ye():document.querySelector(".tsd-hierarchy")&&Ze()}function Ye(){document.addEventListener("click",r=>{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=tt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function Ze(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Qe),Qe())}async function Qe(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await A(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),Ke(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function Ke(t,e,n){let r=e.roots.filter(i=>et(e,i,n));for(let i of r)t.appendChild(_e(e,i,n))}function _e(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let l=t.reflections[a],c=s.appendChild(document.createElement("a"));c.textContent=l.name,c.href=te+l.url,c.className=l.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=te+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let l=_e(t,a,n,r);l&&o.appendChild(l)}}return r.delete(e),s}function et(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function tt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}G(Y,"a[data-toggle]");G(K,".tsd-accordion");G(Z,".tsd-filter-item input[type=checkbox]");var Oe=document.getElementById("tsd-theme");Oe&&Se(Oe);var nt=new J;Object.defineProperty(window,"app",{value:nt});ve();Ce();Pe();})(); +/*! Bundled license information: + +lunr/lunr.js: + (** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + *) + (*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + *) + (*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + *) +*/ diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js new file mode 100644 index 000000000..671dd0d8a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js @@ -0,0 +1 @@ +window.navigationData = "eJytW9tu2zYYfhdfp9uadt0WDAPinBbAXjI7rTEURUFLtMOFpjyKSuINffeBomiREvWTonSV1vxO4lEipc//TQR+FZOzya+ECcwZor9NTiZ7JB4nZ5NdlhYU599/1WVfv3sUOzo5mTwRlk7OTk8mySOhKcdscvb5KLUiLM1eapmEojy3ZRTEVnt7+vO3k6PIeZpePWMmZiQXmGF+txckY3mtWoptUGILd9AauX/8cKJSTc4mIk/fkPwNflUSEzMD5+gwLTYbzD8R/OL1tuFxnlOarX1GEhOvfs+zPebiMEXbECMDHud5xXkW2nwmNtJNtr7XRoIG6N8yEuYhgQN8dC8O8tLgEfzu1n/jJOwKbcoY3oE9ZbRRXio9IL7FYVesoHFetwJztKbYZ6RxcS5znBK0zAqeeI0M6GCvsnbmaN/DU1PivO+znMiG9xlqXMvF1OLZjuT4uqAbQilOFzgvqLdDuFmRV6O0ZuTJ22oGdJDXAstB2/NibdIg/xURjwucZ/QZc++Id3Hi3P8scEHYdik4Enh78Bk34KN4Lsm/3lZ2UOK8FxilcjaZHgReCo7R7iJjgmeU+pcWiDssjVIL9VfoMRynf91N5S/h197kjZfinwLn3nHXSRwjxyXeoIKK/h2igz5ipphGsqhjZLmR9xckicliUcfJUl1Y4D0SzB4jkfzfZcZw2AICccdK8wnRIjqOQR6WZ8WJkH/vEQnuNCYnzj1kUXGuIqZGeV+mnmK9WgY2MrGhEHr76OJEupcNf0/2OHBAtQhxvg9khxeIbbHXsEbGOX1kKeb0QNi2XL+DHglcnKHu1bTcN4BFG5phSdhTuLVEj+F4vs64uECUrlHS096ijpHlgmY5jstiUcfIshQotl4s6hhZ5MwbWS8WdXCWnsNjpHFRqlwglmAaUQkO9jiJ7gsancfkjpMmtr+2yZF5FjOv62IWp63vPMKeyGz0GI69n0E89BEzleM7Lo+iRp487PeYpfMsNWYDcdg3zhuOoIbJD7/89PbH075HHPbGU9vNRg6wfEFE4BSyUoh4i/LcAnEBeGjIAJOyLppzdtvIgMWbqQmWls8Hah+s+sXsnC1vgOWMAhpeveKkEFk/P03qb6e2GXvaaVKMndpS7GmnSD67K5YStn047KGOUoPiu8kVS+82ahYqj9JgOws6wNQ6iuHgSVI7hpccH0yOb4Io4F4hBliojrDEQjgODdp+Dni8ub2B4Vo+WwG6KGOFaO6TeQI498YGmXuboIsyLETG6MFjKiHDTA5LgQQ0hdSgIUZJxqHVWQGGLc61/DPiRDZHa3F+89a2eNdjMvLIl5gh+s3T4m4XhYz0cp7jOr0MZKRX2BmR0xyiDkzTfCIB/RV4FEfXGVGAd00bMUXjjCg4RskbJQfwfBaQpsUeM1NEI1nMyCzuXXKnuQmNdHPtFzu9amCkk7XL4LT4uJhFanftMjhtbPAojn17sYc9ZqbmLkN4HsXsleWLvc9ASYLsl2j0a59GoS3/3v3e5+/E3K7YFCwpz0gsHYmxxT68t96SMF+sc0tIDCCxfDTfPXVLSExLwqyXKc9ecrNFdJ1UBUH1cbfHzBpQdRgtU0HALHLfsB1E/hqUYlEwQXa48Tio38ktZUwI9E6uBLdOytT9YaljlPueiKeH20tXxZQ6shBo4enhD7Rz9rSKLYsBvl2hDXa7XpvNQcl+nSHzFvnYJrooqGGWWDzIf7mSHIUqEHA1fg2ngHlJlwTRbJu3L6gqCLqcaSFEx6t4WkZBoNPna0LxNaFdW65aqIZBYnOc52iLFQk64dWyLgJkIEevjBLu4GSAx/HoGfezcDIgi8bMUPcgreiYF6weeMs2GcSX5QBd1wkkoTHg0oFzex1ry2gMtHxU1QfJaAwgs0KcEbaFVCoIODLLZ0jHwFS/B43LFSI0bzwV69m/kqkhvtnfPvxSU38l0nG81Zr55Qan8z620inLm7czZpXs3LcIFV0WQ51tswHId5sNzD13rxw1/dyxehgKzt6p2VC3vGOJs0seuc3DhQZ7XlBB9u5ufdTQILBLXlPkWirKn4M65A0WEuxKokQqAJhimXCMmSNHVRB2a9T1kYMWWbg+aWilACXUX1Ck662to4Tnla0bLDr6pVZQCKCD3GBxUXBuTRJOmQoFS91zskP84JGqUHAzH3KBd45WLn8PauQr9kx4xnbl1zjmQmXWtJJrQMGlf+kTUwhI4wLt0ZpQIog5IRp1pYRMGFDvRnhAzEABWoQ9Z0/OuaKSUQDoniA/n19+eA9IVAhYYzGHFRZzH9+XQSJAjUvEn+yDeIeMBsFKeF04p71aRiJAjRlhxSuoUSJAjTlKQIU5SkC++ngT6rJHDDi4V/NZe2Sv5rPAYW1/R1VnkAqqFLiKBaYZSrvoqtTzVC4Pgzufqo+F8B1V80C5S8rEBCjKo0V5dEoyBiZsAful7fxUyQ2F58Imw/9tkIcD2TU/Ua7vQl1fJr87/fblf8cMM3U=" \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js new file mode 100644 index 000000000..2d05d6e88 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js @@ -0,0 +1 @@ +window.searchData = "eJy1XW2P2ziS/i/ur31ZF/UeHA7IJDO7ASY32WRmc3eNwUBt093ayJJXkruTDea/H0hJdpEsSSXL+ZSgTVYVyYfFYj2k+G1Vlc/16uXdt9XnrNiuXvq3qyLdy9XL1avDIc82aZOVxep2dazy1cvVvtwec1n/Bf324rHZ56vb1SZP61rWq5er1Z+3vbTwLO5v2Vae5OyOxUZVNiWpIoS429UhrWTRWDaRWj4+ls8TWlSRhVr+fsyaCS2qyCVazkp+UCMjK6fvu79z+/2Xgyx++/AzYW4vqCsxbm1vDWXp6zTPHTPVH7k2GgLOBmoRA3KQabo2KfeHr/+t/jMguf31ctlv3wxLfvtmllyxTiIIhNEhvxy0xJOO5utBoj7pfp6lBkR8UvHhWDTZXv5YVeUZY52UVgsuMUtNAOeWbMqibqrjpmFruTGrDGs0qtLAzLPDfZlWWxed/S9ciH6Uza/yCzXrz6K6MhN9dbKJ1DOp5DINZwVvsjQvH2qnR7q/c/vjbbErCTt7KerncSt7O0jpn9KqyIqHEQVdiQU6TOS7Ghi4H5X/96OsjcXTVdEXWaBFOe+fspxycr2WvsgCLR/TJzmhpS8yV4sIQqcxbSnbAWZFI6tdupFu04wKcy2AtcBLUfH6sSxr+SarpHJBmbzQgptNWmy0qK0hatQsWt6ksarGYjN3nZArG1jJtLlSb2pR3603VWD4t2y7batcamf9WD4/ainfoT8/yLrMn2T9Ks/S+mITq05KepJyRRNf5Xn5XL875k12yOVHmcuN4QXnmZpqaftOWo2kXdFkte/48Usji/pyQx+zrZRIxnUnUduNLTiXWrpJi7YfW5R+J6N/VXO1VsXfp5vP6YOsX9XLnUCjpaqJdeikpvV38wctkn9pHmWl6uj4exGMSyVqh0Rd0difsryR1aX27U61rwmBrEERw8yB7upe0Zx3sq7Th0sN2p9qX9GkH45NUxZGyD/PqnstoGkFXNGwfqZ+vdCuLap/TbNkk24e5fZSq87VlxmFY9Y+7mXHrGSFRTGrklbgRMcsrWry99XHu4Vu6pUC6REDLwqkLzN2MvLjmMkL/GYbOCeQnjBzdiA9y9gZgfSInXMD6VkmzgikR0ycG0jPMpEdlY4YOC8qnYvI2VHpOCovi0pnGb0gKh2xfWlUOqsJc6PSEbsvikpnGcuISscXJlZUOg8CU1Hp2ECzotJZ5jCi0hGDuFHpLJN4UemIVTOi0lmGsaLSEbv4Uek8sxhR6ZhV3Kh0yigclXawYgelVPlFMenkNBvUyJxlZAsvn2TD5nDn2ByD2il22Yh002vaLc4xiIPhYYvYEJ4wCSO47aIxe9oSi1D6c3ovc6aOm7wrPN7Ezu4BhW/r12mx4evM6k1ffpnaN3KXHvNRj27q3Z4qzFSMB1G5qzYGGFN8LrVoMN9k9SFPTcp/WtvNtq3G2gGj9gwY8T5tGlmN4tY24HCqcoFyxGc+yaJxed32z+zTIkWfuSeovk7Uucz4cHUGDegZk79E7mbU8s0Sm3e7EdG73SLJr8gTMGfhr6ZOwYzJ/3FPnlfqpKtfZ8r2zgc8fjU2IE9plaX3CHf655nSqdMw9+nms3UUptPQ/zpTCT4L8ynN8lqXcM6odErOJWaq4Z6FofWwzsI4VYd8k5EjnFA86g7ZGrdpk3I1dmUXaqxlsZXs7j2Vnq8VbXLzlDhPo//Kdbt/lY0qT8zRVkz3+zj2WjsoCz9uKikL18bu7zOspN1UL6ctMG5lb8uQhvdVtk/RFo/U0hVapun1sarwlCc1dYXmajIYguzfZEDSa1G/z5VvYP5Ttm0eWRpunruio3p0+cG0pMweHsk40tX22JedpQ733Qe5GdWlfl/Ud//Dkn7zZbIR2tIBJf/LUzKcl+AomUTBWREPBWPKplFw1sZEgaXOmEG6yCjm9L+LkICO844ruMmGN7pm8SFVQ1sUStnoQsxT93GT5vKn1Ig7JrTWqsouHY07eMpHJxhWOT3FxhWNTjKsaHqaTXTnlD/H/diWXaLuh/JYbMlUEaXwvi+9ROX7x691tknzeaoPXa2rmPCprD6/qmTKVf5cVp/Ttvw1Wj5Xfd/2K5nxtrbDoCmXVB9OFZYo/lA25o2fCb3VufxMtWg+fa0buXeDU/1nbmyaFU/lZ2rr34lpf59YIFpD6PPu9Zu0+vyuJC8xdTrOZS7X8zo9pPdZnjWYjnQ04VKX6/qxeMqqstgPBMGtKlRoSe99yoqtumk20nldkSVafs6K45cxHbrAEg3v0s2Y/HfpZon0V+/ehP6YfF1gkYYP70blf3i3UPqE/arAolko74/UPvk8BVWBmRqMOwgfjast2P+1KtoCMzWYK3qVFlt8vWVUyc39ufiYtq7OzPDWVTcW3XIUDQa3rqrx2Jaj7B+yGjp0Quh7OpWepxLjA/nDCaBYJZcg5lW1obd2w5pu0rbOaEvtxgyyg3jSMfVvu0rXMOCXj3O1lyMB6EzVFwzzTVlnba1rmPA+T5tdWe0vMeTQ1V1mDsr3t6s0kfBvfxiN11B4/+5nJ+D79I66gzwQuSidxDKgZLQ/jk83pZ+U/EHmZbodkNz+OEcyZhla5nfowu3pt9E+4F+tNeUxL9WeDRxrQi63H+Q/25s4Y81xyl2xabTsGc10GzEw+w5Vuc9qlzgZteNc6VIb8JrTlVawft8K/jn7TC6ydMnRfhdr/zzAzSOdZxuRe9PVGW2obfqA+s3g4YgxAyaOSEybMNrVn7LmsTsuTJ9knKgy2vmjSJupgwu6wbYNmNWdcr7YrHP9K5v1zwFygGlVV/2qRpXFpp/ZF1tmylhsnuu+Ua0hr4aK8J0H/orHhLybtiy3cSMaP8qmwb3NUFyfqizWX3zlKy6Gk3N8jVW6YQ/aTVd4qU7LK09pbYv/h7iCZvV5j/LonsoYUn0uv1RznUt5YOvtSy8eXdOnTY7vXB82rNd08dOK57r0Ic3P5Oo6pV/VqlCtC6yYGW66KOdHmt9ril1NLzqNx9M8kkOZMblxrDk5s+cFmcPtbVAmZbqxzUgOha91lxVpnvOXinP5SzSf1P6njjyKNP8vZ6/9R//TH+PhKT4kZ278+zYgSYMZAGQ6smmgs/4qXQdIK7l5kMPO76yoqzKk731ZZwblNaH0cC6/UPNrWTTEWbUBvZu+9FKteUnEfUNKu8ILdb7JagVWxZy/1n4zzfBZ5Qkjtm1txaVvjNoLrWoTRRcaJYvvYtNP5ebItmHXFV6ss9pIK/E1qbnayKqvslT/Mc9r84jPlHpcY6H2v8rm4yzlD7K5ou7/K8v9DM3/bosv1Gsd35pQO3GAi68VfwJ0SmdbdqHGt7WeT8T2cEBtVu9OFZbrno3rrL4mshUd/SVT4cGM9u9RleX6s2K2flRlof6+9Vzl+3P5pZq7RrA1n8sv1GywrxNaR+lXtkb1sZQ38unXsszZC1d5UAfvn5quzkILPsg8bbInOTeSq7p614vo5i2i11o/P6gwxCDEJhWfKyzX3ZTVHM1d8YV6P8pm7nDXsrneSH9UVx2e06/1L8WvpZsoGjYh1bXKoimHE0ZzrPgh3Xx+qNSRy9dlXh7Z24laNvenqpu+6nJ7fqrSvcxlzfYFtWx2qM4VLDgtoe3d1DbMZ89KZc9JQnvXWp4kLLfuXfrFOEA8bc0+/TJ6jHiW9qyYqz0rrqb9Ukddy+b6vlrbM9NxakOu5js/ymbmYFxtJMzPM0wrHv8mwxzNc/Y79bX2O8azAFNK27JLNc4Z2KuM6q/lw0Mu5+85Gl3vmvuO1pK50Xdrx/Vi8K4/5i5HXXdcb0X6rZg/JsfimuPxWzF3LI7F9cbht2LubuhYXG8/ZN6Km1A8fi+OrXOOi7uOf1Ma37LRpXRmV8CV0voLwc6OqB0jZ2fp/SBrPk+gNFddhUt0258jsNmXnsA82dN+oABZM1znIrpk2qB/SoPNZBnU1vkuBvXn3WZZdKr0XUz68YvcHGf2UV/nEoPQITMn/kVHhJD+vtgF2oxDSV/m6Bm5h4kmyakFAyrJi2uDKoeZznGVnFv2SOn0RfvpvnweumVN6eEvKGM37h8H71qTOvnZ+lbp77errNjKL6uX31b9BYmXK/HCe5Gsble7TOZb9UDYqs8Wlvvuwti23Bz1f3/viv1D+w9VuC39l/Xq9m59G6xfeCL8/ffbu76y/kH/oZdx/ouuCKvbO7j1gxcCYqMiOBXBqChWt3eCqiicisKo6K1u7zzKVM+p6BkV/dXtnU9V9J2KvlExWN3eBVTFwKkYGBXD1e1dSLUxdCqGRsVoqGLkVIyMivHq9i6iTI2dirFRMVnd3sVUxcSpmJgAUHhIqJrgYgcs8Gj0kLgDAj8mgEDBAuDWi154nmdWdjEEJohAQQMEqdnFEZhAAgUPIDEILpbABBMoiACJQ3DxBCagQMEESCyCiykwQQUKKhCSlV1cgQksUHABElrgYgtMcIGCDJDwAhdfYAJMKMwAiTDhIkyYCBMKM4JEmHARJiwXpX0UkJUJL2UiTCjMCBJhwkWYMBEmFGaER7pHF2HCRJhQmBE+WdlFmDARJhRmREBWdhEmTIQJhRlBOi7hIkyYCBMKMyIiK7sIEybChMKMIBEmXIQJE2GewoxIKM2eizDPRJinMOOtycouwjwTYZ7CjEeun56LMM9aCPVKSK6hHrEWmgjzFGY8EmGeizDPRJinMOP5tz68iAPT9XouwjwTYZ7CjBeQlV2EeSbCPIUZj0SY5yLMMxHmKcx4JMI8F2GeiTBPYcaLSbNdhHkmwnyFGY/0Yb6LMN9EmK8w45M+zHcR5psI88WgJ/FdhPkmwnxv0JP4LsJ8K9zyBz2JT0RcJsL8YNCT+C7CfBNhfjjoSXwXYb6JMD8adAa+izDfRJgfDzoD30WYbyLMTwadge8izDcRFqwHnUHgIiwwERbAoDMIXIQFJsICMegMAhdhgYmwwBt0BoGLsMBEWOAPOoPARVhgBfXBoDMIiLjeRFgQDjqDwEVYYCIsUJjxybAicBEWmAgL4uHedhEWmAgLkuHedhEWmAgLFWZ8MqAJXYSFJsJCGOyw0EVYaCIsVJjxyXg7dBEWmggLFWZ8Mt4OXYSFJsJCvWck4+3QRVhoIixUmPHJeDt0ERZaW0eFGZ+Mt0Ni92giLNQII6Oh0EVYaCIsVJjxybUqdBEWmggLFWYCcq0KXYSFJsIihZmAnBiRi7DIRFikMBOQ8IxchEUmwiKFmYBEWOQiLDIRFinMBCTCIhdhkYmwSGEmIBEWuQiLTIRFOjNBIixyERaZCIsUZgISYZGLsMhKUCjMBCTCIiJHYSIsUpgJSIRFLsIiE2FRMrjtj1yERSbCYoWZcH3rJS8836gbuwCLTYDFCjIhic7YBVhsAixWkAlJdMYuwGITYLGCTEiiM3YBFpsAixVkQhKdsQuw2ARYrCATkuiMXYDFJsBinf4i0Rm7AItNgMU6BUanslyAxVYWTEEmJNEZE4kwE2CxgkyYUBhx8RWb+EoUZCIKX4mLr8TEV6IQEwFV14VXYsIrUYCJSHglLrwSE16JAkzkUWtz4sIrMeGVKMBEZDySuPBKTHglwWB3uehKTHQl4WB3ueBKTHAlCi4RCevEBVdigivROVYy6kxccCVWmjUZckAJkWi1M60aXXQObk0lW61s6xoGB7r9za5vJVzXYnCs29/s+lbOde0Nzaz2J7u6lXVda6jRWcQ1kXddW4nXtUbbQKqaSL2urdzrWoEoprPVayL7urbSr2uFo5hcQNrf7PpWBnatoBTTOes1kYNdW0nYtc7x02nrNZGGXVvw08n7mM5cU7l+J9mv4BTTyWsy3W/BT6fwYzp/TWX87ZS/zuLH9PShkv521l8n8mMaf1Te307861x+TOOPSv3buX+dzk8G2BICf3b6X2f0Exp/FAFgMwA6qZ/Q+KM4AJsE0Hn9hMYfRQNYPADo1H5C449gAsCiAkBn9xMafwQZABYbADrBn5COHwg+ACxCAHSOP6HxR1ACYHECoNP89MIDBCsAFi0AOtNPrj1A8AJgEQOgc/0JDX+CGgCLGwCd7k9o+BPsAFj0AOiMP6xp/BMMAVgUAeisP6zpCUCwBGDRBKAz/7AeYA0JBFpUAejs/wCCCLIALLYANAEAa3oKEYQBWIwBaBIA1mRGFwjSACzWADQRAGt6EhHEAVjMAWgyANa0FyfIA7DYA9CEAKzJxB0QBAJYDAJoUgDWNJAJEgEsFgE0MTBAPBM8AlhEAngtDumZQHAJYJEJoPmBAQMIOgEsPgE0RQBApqmBoBTA4hTAb6l3eiYRtAJYvAJoqgCATFcDQS2AxS2ApgsA6FiWoBfA4hdAUwYwwMMTFANYHANo2mCwEwkcWjwDaOoAgCRIgKAawOIawG9xSLsTgm4Ai28Av02l0CsSQTmAxTlA0AKRnkoE7QAW7wBBi0R6KhDUA1jcA2g6AWiCHwj6ASz+AYL2IAgNZYKCAIuDAE0rAE30A0FDgMVDQBCMQJmgIsDiIkDTCyDoVYGgI8DiI0BTDCAGzqQQSLQ4CQjGkEjQEmDxEhAkI3OBoCbA4iZA0w0gBk7GEEi0+AkIYdinEgwFWBQFhGJkFAmWAiyaAsIWiPTCSDAVYFEVEPojPpVgK8CiK0AzECBod0AwFmBRFhC2QKTdAcFagEVbQNgCkXYHBHMBFnUBYTwS3xDsBVj0BWhGAjzanxAMBlgUBmhWAmgWFwgWAywaAzQzAR7tTwgmAywqAzQ7AR7tDgg2Ayw6AzRDAR7tDghGAyxKA6L2fBw9GQlWAyxaAzRTAd7AUTUCiRa1AZqtAI+GMsFugEVvgGYswKOhTDAcYFEcoFkLoI98AMFygEVzQMtz0Mc+gGA6wKI6QNMXZIIUCLIDLLYD4uGTvQTdARbfAZrCAJrlBoLyAIvzAE1jAM03A0F7gMV7gKYygKaNgaA+wOI+IG5PatLzgKA/wOI/QFMaQNPHQFAgYHEgoGkNoClkIGgQsHgQaIkQcstPECFgMSEQtxgcOPVJYNCiQ0BTHEAz0UBQImBxIpDAyG6P4EXAIkYgaXFIz0OCGwGLHAHNdwBNaQPBj4BFkIDmPICmtYHgSMAiSUATH0BT20AQJWAxJZC0p4bpmUCwJWDRJaAZEKApbiAYE7AoE9AsCNA0NxCsCVi0CWgqBGiqGwjqBCzuRGguBGi6WxDkibDIE6HJEKApb0GwJ8JiT4RmQ4CmvQVBnwiLPhGaD4GQPsxMECjCIlCEJkSAprAFwaAIi0ERmhEBmsYWBIUiLApFaEqEvvVBMCjCYlCEZkRIAkkQBIqwCBShCRGgiXRBMCjCYlCEZkSAJtMFQaEIi0IRmhIBmlAXBIciLA5FtDcmaFJdECSKsEgU0d6aoIl1QbAowmJRRHtzgibXBUGjCItGEZoWIUlfQbAowmJRRHt/IqSnEUGjCItGEe0dimjgTgCBQotHEe09ioieRgSRIiwiRXR3KehpRDApwmJSRHufIqKBTFApwr5S0d6piGggU7cq7GsVmhsBmkIX1M0K52qFxmFEA5m8XWHhUHjDGy1B3bCwr1i0dAq5YRfUJQv7loXmR2g2XFD3LOyLFpofoZ0ZddPCvmqh6RF6GlF3LezLFpocIXcHgrpt0f1N30B8klUjt2/bm4h3d+3H5L+t/ujuJqp0nlairimqhN3Lb3/+eb6N+PLbn+hCovpNaUrzvHyu98e8yQ65rGXePrWB5Z4/cPttJZIZUsvmUVa7LJf62jAWeX4s59vK81ohwZor+vRxetR2scZtFzxR6nPzWAZgGR5PxuGQZ5vU7jNkDbNZ+ikqZMwaN0jELCH9s6JnMREaO3UugyXl9JjaWU4SYWt4XXNflc+1us5+FuOfpTBt0R+XMkR4Zxkhb5xbIUa/+AiAIW+EWimN/NIYSA4RkqMOycy2fc0MDKOh4s2y+6+6PB7uswgeYjaWD0GtCdsqEVuO+lYalhWgBkV8MeVBvyZloA+DmCmo/fo79o4IOIqV0hVVTr/9DxNLrWDZPumEpyueaB63taePJ5zepcEGY5FM7+iK3Jw/K4FkRwHy6YK3VLiy5en7EFh0iEXP6VUsOtevN+HewM7Zv1RsdfrqB7bYxxZf2tHV6QsnWDQCnQoGLxNtveWAe8XHvRLMkS+3Vf+wFgFnPOW8OXab3lrxKmdBIW/hUIIey7KW26zSo5WZ0YPAndpN5BmI0KJVWGIKxSDonILPt7eSaTNoL55snVv1eWvEJi3amOwx225lIb80sqitWMNDkYIHnXiu+8dPKaOlCC/3wO2FxgphBJYSMsHZfcIficHBoiJdWGLy7HBfppUVJGJQM+WUllf28DISMVGHH0LBsxcHVd1ym3TrkRd3//HX/QrFa/o2bVJjSUdaYqaI9l1S7A8MU3n+YCubdPNoxukeWtO8rqlBNyW47cvSvHww3WCAgcaU0k5VYwfgIcD2/R/wcDv0AANGDvbVEQ9/26w+5KkT5/moG0NesCH3mRG4BsgxR7w2yu5JUYwL7N69OWJGewqPZ8TzlBK/1I48GQ6iYLao9k1YFInidVEwe95ZXbFNvLkkn6TVSQHy+iFPiFrzdllu+VcfgT7kTR4lyIEkssfnzeTWFjMxgLyr1y3tzEzG6UUg7PBxT3Mbl6emdwmRTTFv2navjmBI44nCnG/GCyJYFl5XI2ar0McysSjcQcxNmn5SCMvAmQomFB/UV77NnSfqoJjXpgfZbI5VZc34EDUp5oVBD7JRo25IQc465rmNB9kcqmyfmmtKiPfk7A6mhgq7nojdsPbrmUgOnqgQ8Yar/2AayjSggCDpgs6kW8jVkQb9H8HMW7UvmSAbkYW9SJ4TUJLIIFmg3vM683zecJhZGry9TfoYTfAQYq8mYDgFpoj203Smm8PQYMYDWfFUmlvsGK+6wOvvrE73W3WeDS2ROMoGHgCyOq0MlCY4ymaukVqIZQsOuZg7+6x2k0c+6pqQOdb1Nq0+70sT2TEeceDtHrLaiccTPFTM9I0Ss0uPuTGNfZzw461K6P0f7FXwnI25DRtYlXB2FphLblbnWXH8YvQS9nTMfYF63WdjCMEN405S/EQQbhdGNXNVMZ77wbIwtGPu0BELVIw7e82dr8/6E8Bm0gB7IeBZlKf31kxDzWKmi87f4cbdg2cIM57Yy7pOHww5eCfidctb0BNWPLfUSW23rURyG8+cgIev87e/cYuxa2FGGnYQH6IZ0y/iSZ+R6LNiwIyqyt3O2LCgkWXufsvdzooSAzQezI2hGQcECO3MXJLx2DjO3uAw3OeBoSw2RocHyCMwk0ll0bO1hiCEooi30JkvTGEk4YbFzIYdZKG2hUbMhdDEXKR6MYOzRaDxY/KQSqb6LjDuLTTleDLMHlobmURmd9dOMgHPWNElAMHj9dQhbVQkaHgS5IBD3hw9PH6ts02aE8yxQbrxeqmX9lxWn9NKGmnIGK+ja557OuRpsyurvRM3r/FC6vHmzflZHLypwlZFp20LD1gkaYdjDr/fvfi8BehfR1k7JqJxYEaw/zqaqT6cU2EJqFLTSwGedcCktyv97joCFJKR8Iasf7wd9y+eM36//wp4aHdfSMJez8jK8XqaSM6ssfPsQwZIeJMavZyELcNTkd113fPzuO9wTORHfd/xHHwnsE7zLK0tfgs1WXRDwkT86bE7nMzHUyhhIrZsnDM4MXYSa2Yrj0WT7SVBiOLYliWqTp+ksyIid8Okxnsxgysizgv5PNjWmzSXu9TmoyIM3TVXlL17w0cLEp6fb4WYSVckhbm7qVU4YzQoRC6HuSe1nx/EuMROMOHaRDwhiGVijCa8OWO+BoiF4S0ht+tHnvLDorHrSHjeFr/Lh0VhD57wpiV+ZA+Lwo6HC1j8xCWWhSOBhLfEkY/uYZn45OSa6TaM9/OwMCMQYKPZ6TS8x1M3FZhy7INv5nEqtpT2kTxsDnaJzBOKNZHWxnNdMOPV9h075J6RiC4UXDPn5GP53B7RcA+W4PNQ3aLLPKljj12IIBD32/K115sa9eEr0+ZcStPD4TMwwMxF1F/rRhpDEWMpzPN6DroMS3giHq0sonHcw+96CZjpWxUF6Je4cPdgyAe8TnYAj08BeF1GJejQxgxjnff38DTAZ424ne88b4gF4sNWzPSg/U4hFoePWTGDskYdrqrVxDqkm8/pg6zTeuCoFd7PeV2vMlMzziF1vLowWVbzUULcbOwEmAEkfmEQi8KLHpNCwM8FYlF4fWKeiOrfP8J5DLxzYjqf5zTLa32qwehy5OCYFOdzWhXWYXUjMceU0r5ChaJYtEomfSa0m64C/N7T8nxkmyw39xPYwXkn58S1dug8qDBOQvGm//PeZBPXWIbHc5dkxgfvmZjrqMHf4MOzSd9Fa+j7nmeZwXXgk75JfwR83a/1grfQuKEHjsyYq1X/wCRGrZEu5fXX6cFILAdHVEzGCj3/iCXhgIgzk36/XR2yg8yzQq5e3v3+55//DyOpVPU="; \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css new file mode 100644 index 000000000..2ab8b836e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/style.css @@ -0,0 +1,1611 @@ +@layer typedoc { + :root { + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-warning-text: #222; + --light-color-background-warning: #e6e600; + --light-color-accent: #c5c7c9; + --light-color-active-menu-item: var(--light-color-accent); + --light-color-text: #222; + --light-color-text-aside: #6e6e6e; + + --light-color-icon-background: var(--light-color-background); + --light-color-icon-text: var(--light-color-text); + + --light-color-comment-tag-text: var(--light-color-text); + --light-color-comment-tag: var(--light-color-background); + + --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; + + --light-color-ts-keyword: #056bd6; + --light-color-ts-project: #b111c9; + --light-color-ts-module: var(--light-color-ts-project); + --light-color-ts-namespace: var(--light-color-ts-project); + --light-color-ts-enum: #7e6f15; + --light-color-ts-enum-member: var(--light-color-ts-enum); + --light-color-ts-variable: #4760ec; + --light-color-ts-function: #572be7; + --light-color-ts-class: #1f70c2; + --light-color-ts-interface: #108024; + --light-color-ts-constructor: var(--light-color-ts-class); + --light-color-ts-property: #9f5f30; + --light-color-ts-method: #be3989; + --light-color-ts-reference: #ff4d82; + --light-color-ts-call-signature: var(--light-color-ts-method); + --light-color-ts-index-signature: var(--light-color-ts-property); + --light-color-ts-constructor-signature: var( + --light-color-ts-constructor + ); + --light-color-ts-parameter: var(--light-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --light-color-ts-type-parameter: #a55c0e; + --light-color-ts-accessor: #c73c3c; + --light-color-ts-get-signature: var(--light-color-ts-accessor); + --light-color-ts-set-signature: var(--light-color-ts-accessor); + --light-color-ts-type-alias: #d51270; + /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; + + --light-color-alert-note: #0969d9; + --light-color-alert-tip: #1a7f37; + --light-color-alert-important: #8250df; + --light-color-alert-warning: #9a6700; + --light-color-alert-caution: #cf222e; + + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-background-warning: #bebe00; + --dark-color-warning-text: #222; + --dark-color-accent: #9096a2; + --dark-color-active-menu-item: #5d5d6a; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-icon-text: var(--dark-color-text); + + --dark-color-comment-tag-text: var(--dark-color-text); + --dark-color-comment-tag: var(--dark-color-background); + + --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; + + --dark-color-ts-keyword: #3399ff; + --dark-color-ts-project: #e358ff; + --dark-color-ts-module: var(--dark-color-ts-project); + --dark-color-ts-namespace: var(--dark-color-ts-project); + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-enum-member: var(--dark-color-ts-enum); + --dark-color-ts-variable: #798dff; + --dark-color-ts-function: #a280ff; + --dark-color-ts-class: #8ac4ff; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-constructor: var(--dark-color-ts-class); + --dark-color-ts-property: #ff984d; + --dark-color-ts-method: #ff4db8; + --dark-color-ts-reference: #ff4d82; + --dark-color-ts-call-signature: var(--dark-color-ts-method); + --dark-color-ts-index-signature: var(--dark-color-ts-property); + --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); + --dark-color-ts-parameter: var(--dark-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --dark-color-ts-type-parameter: #e07d13; + --dark-color-ts-accessor: #ff6060; + --dark-color-ts-get-signature: var(--dark-color-ts-accessor); + --dark-color-ts-set-signature: var(--dark-color-ts-accessor); + --dark-color-ts-type-alias: #ff6492; + /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; + + --dark-color-alert-note: #0969d9; + --dark-color-alert-tip: #1a7f37; + --dark-color-alert-important: #8250df; + --dark-color-alert-warning: #9a6700; + --dark-color-alert-caution: #cf222e; + + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; + } + + @media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-background-secondary: var( + --light-color-background-secondary + ); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + + --color-icon-background: var(--light-color-icon-background); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-alert-note: var(--light-color-alert-note); + --color-alert-tip: var(--light-color-alert-tip); + --color-alert-important: var(--light-color-alert-important); + --color-alert-warning: var(--light-color-alert-warning); + --color-alert-caution: var(--light-color-alert-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + } + + @media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-background-secondary: var( + --dark-color-background-secondary + ); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + + --color-icon-background: var(--dark-color-icon-background); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-alert-note: var(--dark-color-alert-note); + --color-alert-tip: var(--dark-color-alert-tip); + --color-alert-important: var(--dark-color-alert-important); + --color-alert-warning: var(--dark-color-alert-warning); + --color-alert-caution: var(--dark-color-alert-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + } + + html { + color-scheme: var(--color-scheme); + } + + body { + margin: 0; + } + + :root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-icon-text: var(--light-color-icon-text); + + --color-comment-tag-text: var(--light-color-text); + --color-comment-tag: var(--light-color-background); + + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-project: var(--light-color-ts-project); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-reference: var(--light-color-ts-reference); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --color-note: var(--light-color-note); + --color-tip: var(--light-color-tip); + --color-important: var(--light-color-important); + --color-warning: var(--light-color-warning); + --color-caution: var(--light-color-caution); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } + + :root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-icon-text: var(--dark-color-icon-text); + + --color-comment-tag-text: var(--dark-color-text); + --color-comment-tag: var(--dark-color-background); + + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-project: var(--dark-color-ts-project); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-reference: var(--dark-color-ts-reference); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --color-note: var(--dark-color-note); + --color-tip: var(--dark-color-tip); + --color-important: var(--dark-color-important); + --color-warning: var(--dark-color-warning); + --color-caution: var(--dark-color-caution); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } + + *:focus-visible, + .tsd-accordion-summary:focus-visible svg { + outline: 2px solid var(--color-focus-outline); + } + + .always-visible, + .always-visible .tsd-signatures { + display: inherit !important; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.2; + } + + h1 { + font-size: 1.875rem; + margin: 0.67rem 0; + } + + h2 { + font-size: 1.5rem; + margin: 0.83rem 0; + } + + h3 { + font-size: 1.25rem; + margin: 1rem 0; + } + + h4 { + font-size: 1.05rem; + margin: 1.33rem 0; + } + + h5 { + font-size: 1rem; + margin: 1.5rem 0; + } + + h6 { + font-size: 0.875rem; + margin: 2.33rem 0; + } + + dl, + menu, + ol, + ul { + margin: 1em 0; + } + + dd { + margin: 0 0 0 34px; + } + + .container { + max-width: 1700px; + padding: 0 2rem; + } + + /* Footer */ + footer { + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; + } + footer > p { + margin: 0 1em; + } + + .container-main { + margin: 0 auto; + /* toolbar, footer, margin */ + min-height: calc(100vh - 41px - 56px - 4rem); + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + @keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } + } + @keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + @keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } + } + @keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } + } + @keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } + } + body { + background: var(--color-background); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + color: var(--color-text); + } + + a { + color: var(--color-link); + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; + } + a.tsd-anchor-link { + color: var(--color-text); + } + + code, + pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; + } + + pre { + position: relative; + white-space: pre-wrap; + word-wrap: break-word; + padding: 10px; + border: 1px solid var(--color-accent); + margin-bottom: 8px; + } + pre code { + padding: 0; + font-size: 100%; + } + pre > button { + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + transition: opacity 0.1s; + box-sizing: border-box; + } + pre:hover > button, + pre > button.visible { + opacity: 1; + } + + blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; + } + + .tsd-typography { + line-height: 1.333em; + } + .tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; + } + .tsd-typography .tsd-index-panel h3, + .tsd-index-panel .tsd-typography h3, + .tsd-typography h4, + .tsd-typography h5, + .tsd-typography h6 { + font-size: 1em; + } + .tsd-typography h5, + .tsd-typography h6 { + font-weight: normal; + } + .tsd-typography p, + .tsd-typography ul, + .tsd-typography ol { + margin: 1em 0; + } + .tsd-typography table { + border-collapse: collapse; + border: none; + } + .tsd-typography td, + .tsd-typography th { + padding: 6px 13px; + border: 1px solid var(--color-accent); + } + .tsd-typography thead, + .tsd-typography tr:nth-child(even) { + background-color: var(--color-background-secondary); + } + + .tsd-alert { + padding: 8px 16px; + margin-bottom: 16px; + border-left: 0.25em solid var(--alert-color); + } + .tsd-alert blockquote > :last-child, + .tsd-alert > :last-child { + margin-bottom: 0; + } + .tsd-alert-title { + color: var(--alert-color); + display: inline-flex; + align-items: center; + } + .tsd-alert-title span { + margin-left: 4px; + } + + .tsd-alert-note { + --alert-color: var(--color-alert-note); + } + .tsd-alert-tip { + --alert-color: var(--color-alert-tip); + } + .tsd-alert-important { + --alert-color: var(--color-alert-important); + } + .tsd-alert-warning { + --alert-color: var(--color-alert-warning); + } + .tsd-alert-caution { + --alert-color: var(--color-alert-caution); + } + + .tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); + } + .tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; + } + .tsd-breadcrumb a:hover { + text-decoration: underline; + } + .tsd-breadcrumb li { + display: inline; + } + .tsd-breadcrumb li:after { + content: " / "; + } + + .tsd-comment-tags { + display: flex; + flex-direction: column; + } + dl.tsd-comment-tag-group { + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; + } + dl.tsd-comment-tag-group dt { + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; + } + dl.tsd-comment-tag-group dd { + margin: 0; + } + code.tsd-tag { + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; + } + h1 code.tsd-tag:first-of-type { + margin-left: 0.25em; + } + + dl.tsd-comment-tag-group dd:before, + dl.tsd-comment-tag-group dd:after { + content: " "; + } + dl.tsd-comment-tag-group dd pre, + dl.tsd-comment-tag-group dd:after { + clear: both; + } + dl.tsd-comment-tag-group p { + margin: 0; + } + + .tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; + } + .tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; + } + + .tsd-filter-visibility h4 { + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; + } + .tsd-filter-item:not(:last-child) { + margin-bottom: 0.5rem; + } + .tsd-filter-input { + display: flex; + width: -moz-fit-content; + width: fit-content; + align-items: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + } + .tsd-filter-input input[type="checkbox"] { + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; + } + .tsd-filter-input input[type="checkbox"]:disabled { + pointer-events: none; + } + .tsd-filter-input svg { + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. + Don't remove unless you know what you're doing. */ + opacity: 0.99; + } + .tsd-filter-input input[type="checkbox"]:focus-visible + svg { + outline: 2px solid var(--color-focus-outline); + } + .tsd-checkbox-background { + fill: var(--color-accent); + } + input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { + stroke: var(--color-text); + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; + } + .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { + stroke: var(--color-accent); + } + + .settings-label { + font-weight: bold; + text-transform: uppercase; + display: inline-block; + } + + .tsd-filter-visibility .settings-label { + margin: 0.75rem 0 0.5rem 0; + } + + .tsd-theme-toggle .settings-label { + margin: 0.75rem 0.75rem 0 0; + } + + .tsd-hierarchy h4 label:hover span { + text-decoration: underline; + } + + .tsd-hierarchy { + list-style: square; + margin: 0; + } + .tsd-hierarchy-target { + font-weight: bold; + } + .tsd-hierarchy-toggle { + color: var(--color-link); + cursor: pointer; + } + + .tsd-full-hierarchy:not(:last-child) { + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid var(--color-accent); + } + .tsd-full-hierarchy, + .tsd-full-hierarchy ul { + list-style: none; + margin: 0; + padding: 0; + } + .tsd-full-hierarchy ul { + padding-left: 1.5rem; + } + .tsd-full-hierarchy a { + padding: 0.25rem 0 !important; + font-size: 1rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-full-hierarchy svg[data-dropdown] { + cursor: pointer; + } + .tsd-full-hierarchy svg[data-dropdown="false"] { + transform: rotate(-90deg); + } + .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul { + display: none; + } + + .tsd-panel-group.tsd-index-group { + margin-bottom: 0; + } + .tsd-index-panel .tsd-index-list { + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; + } + @media (max-width: 1024px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } + } + @media (max-width: 768px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } + } + .tsd-index-panel .tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; + } + + .tsd-flag { + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; + } + + .tsd-anchor { + position: relative; + top: -100px; + } + + .tsd-member { + position: relative; + } + .tsd-member .tsd-anchor + h3 { + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; + } + + .tsd-navigation.settings { + margin: 1rem 0; + } + .tsd-navigation > a, + .tsd-navigation .tsd-accordion-summary { + width: calc(100% - 0.25rem); + display: flex; + align-items: center; + } + .tsd-navigation a, + .tsd-navigation summary > span, + .tsd-page-navigation a { + display: flex; + width: calc(100% - 0.25rem); + align-items: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + box-sizing: border-box; + } + .tsd-navigation a.current, + .tsd-page-navigation a.current { + background: var(--color-active-menu-item); + } + .tsd-navigation a:hover, + .tsd-page-navigation a:hover { + text-decoration: underline; + } + .tsd-navigation ul, + .tsd-page-navigation ul { + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; + } + .tsd-navigation li, + .tsd-page-navigation li { + padding: 0; + max-width: 100%; + } + .tsd-navigation .tsd-nav-link { + display: none; + } + .tsd-nested-navigation { + margin-left: 3rem; + } + .tsd-nested-navigation > li > details { + margin-left: -1.5rem; + } + .tsd-small-nested-navigation { + margin-left: 1.5rem; + } + .tsd-small-nested-navigation > li > details { + margin-left: -1.5rem; + } + + .tsd-page-navigation-section { + margin-left: 10px; + } + .tsd-page-navigation-section > summary { + padding: 0.25rem; + } + .tsd-page-navigation-section > div { + margin-left: 20px; + } + .tsd-page-navigation ul { + padding-left: 1.75rem; + } + + #tsd-sidebar-links a { + margin-top: 0; + margin-bottom: 0.5rem; + line-height: 1.25rem; + } + #tsd-sidebar-links a:last-of-type { + margin-bottom: 0; + } + + a.tsd-index-link { + padding: 0.25rem 0 !important; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; + color: var(--color-text); + } + .tsd-accordion-summary { + list-style-type: none; /* hide marker on non-safari */ + outline: none; /* broken on safari, so just hide it */ + } + .tsd-accordion-summary::-webkit-details-marker { + display: none; /* hide marker on safari */ + } + .tsd-accordion-summary, + .tsd-accordion-summary a { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + + cursor: pointer; + } + .tsd-accordion-summary a { + width: calc(100% - 1.5rem); + } + .tsd-accordion-summary > * { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + } + .tsd-accordion .tsd-accordion-summary > svg { + margin-left: 0.25rem; + vertical-align: text-top; + } + /* + * We need to be careful to target the arrow indicating whether the accordion + * is open, but not any other SVGs included in the details element. + */ + .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h1 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h2 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h3 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h4 > svg:first-child, + .tsd-accordion:not([open]) > .tsd-accordion-summary > h5 > svg:first-child { + transform: rotate(-90deg); + } + .tsd-index-content > :not(:first-child) { + margin-top: 0.75rem; + } + .tsd-index-heading { + margin-top: 1.5rem; + margin-bottom: 0.75rem; + } + + .tsd-no-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + .tsd-kind-icon { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; + } + .tsd-signature > .tsd-kind-icon { + margin-right: 0.8rem; + } + + .tsd-panel { + margin-bottom: 2.5rem; + } + .tsd-panel.tsd-member { + margin-bottom: 4rem; + } + .tsd-panel:empty { + display: none; + } + .tsd-panel > h1, + .tsd-panel > h2, + .tsd-panel > h3 { + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; + } + .tsd-panel > h1.tsd-before-signature, + .tsd-panel > h2.tsd-before-signature, + .tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: none; + } + + .tsd-panel-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group { + margin: 2rem 0; + } + .tsd-panel-group.tsd-index-group details { + margin: 2rem 0; + } + .tsd-panel-group > .tsd-accordion-summary { + margin-bottom: 1rem; + } + + #tsd-search { + transition: background-color 0.2s; + } + #tsd-search .title { + position: relative; + z-index: 2; + } + #tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; + } + #tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); + } + #tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; + } + #tsd-search .field input, + #tsd-search .title, + #tsd-toolbar-links a { + transition: opacity 0.2s; + } + #tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + } + #tsd-search .results li { + background-color: var(--color-background); + line-height: initial; + padding: 4px; + } + #tsd-search .results li:nth-child(even) { + background-color: var(--color-background-secondary); + } + #tsd-search .results li.state { + display: none; + } + #tsd-search .results li.current:not(.no-results), + #tsd-search .results li:hover:not(.no-results) { + background-color: var(--color-accent); + } + #tsd-search .results a { + display: flex; + align-items: center; + padding: 0.25rem; + box-sizing: border-box; + } + #tsd-search .results a:before { + top: 10px; + } + #tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; + } + #tsd-search.has-focus { + background-color: var(--color-accent); + } + #tsd-search.has-focus .field input { + top: 0; + opacity: 1; + } + #tsd-search.has-focus .title, + #tsd-search.has-focus #tsd-toolbar-links a { + z-index: 0; + opacity: 0; + } + #tsd-search.has-focus .results { + visibility: visible; + } + #tsd-search.loading .results li.state.loading { + display: block; + } + #tsd-search.failure .results li.state.failure { + display: block; + } + + #tsd-toolbar-links { + position: absolute; + top: 0; + right: 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + } + #tsd-toolbar-links a { + margin-left: 1.5rem; + } + #tsd-toolbar-links a:hover { + text-decoration: underline; + } + + .tsd-signature { + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; + } + + .tsd-signature-keyword { + color: var(--color-ts-keyword); + font-weight: normal; + } + + .tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; + } + + .tsd-signature-type { + font-style: italic; + font-weight: normal; + } + + .tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; + } + .tsd-signatures .tsd-signature { + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; + } + .tsd-signatures .tsd-index-signature:not(:last-child) { + margin-bottom: 1em; + } + .tsd-signatures .tsd-index-signature .tsd-signature { + border-width: 1px; + } + .tsd-description .tsd-signatures .tsd-signature { + border-width: 1px; + } + + ul.tsd-parameter-list, + ul.tsd-type-parameter-list { + list-style: square; + margin: 0; + padding-left: 20px; + } + ul.tsd-parameter-list > li.tsd-parameter-signature, + ul.tsd-type-parameter-list > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; + } + ul.tsd-parameter-list h5, + ul.tsd-type-parameter-list h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; + } + .tsd-sources { + margin-top: 1rem; + font-size: 0.875em; + } + .tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; + } + .tsd-sources ul { + list-style: none; + padding: 0; + } + + .tsd-page-toolbar { + position: sticky; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; + } + .tsd-page-toolbar a { + color: var(--color-text); + text-decoration: none; + } + .tsd-page-toolbar a.title { + font-weight: bold; + } + .tsd-page-toolbar a.title:hover { + text-decoration: underline; + } + .tsd-page-toolbar .tsd-toolbar-contents { + display: flex; + justify-content: space-between; + height: 2.5rem; + margin: 0 auto; + } + .tsd-page-toolbar .table-cell { + position: relative; + white-space: nowrap; + line-height: 40px; + } + .tsd-page-toolbar .table-cell:first-child { + width: 100%; + } + .tsd-page-toolbar .tsd-toolbar-icon { + box-sizing: border-box; + line-height: 0; + padding: 12px 0; + } + + .tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: + opacity 0.1s, + background-color 0.2s; + vertical-align: bottom; + cursor: pointer; + } + .tsd-widget:hover { + opacity: 0.9; + } + .tsd-widget.active { + opacity: 1; + background-color: var(--color-accent); + } + .tsd-widget.no-caption { + width: 40px; + } + .tsd-widget.no-caption:before { + margin: 0; + } + + .tsd-widget.options, + .tsd-widget.menu { + display: none; + } + input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; + } + input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; + } + + img { + max-width: 100%; + } + + .tsd-member-summary-name { + display: inline-flex; + align-items: center; + padding: 0.25rem; + text-decoration: none; + } + + .tsd-anchor-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + color: var(--color-text); + } + + .tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; + } + + .tsd-member-summary-name:hover > .tsd-anchor-icon svg, + .tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; + } + + .deprecated { + text-decoration: line-through !important; + } + + .warning { + padding: 1rem; + color: var(--color-warning-text); + background: var(--color-background-warning); + } + + .tsd-kind-project { + color: var(--color-ts-project); + } + .tsd-kind-module { + color: var(--color-ts-module); + } + .tsd-kind-namespace { + color: var(--color-ts-namespace); + } + .tsd-kind-enum { + color: var(--color-ts-enum); + } + .tsd-kind-enum-member { + color: var(--color-ts-enum-member); + } + .tsd-kind-variable { + color: var(--color-ts-variable); + } + .tsd-kind-function { + color: var(--color-ts-function); + } + .tsd-kind-class { + color: var(--color-ts-class); + } + .tsd-kind-interface { + color: var(--color-ts-interface); + } + .tsd-kind-constructor { + color: var(--color-ts-constructor); + } + .tsd-kind-property { + color: var(--color-ts-property); + } + .tsd-kind-method { + color: var(--color-ts-method); + } + .tsd-kind-reference { + color: var(--color-ts-reference); + } + .tsd-kind-call-signature { + color: var(--color-ts-call-signature); + } + .tsd-kind-index-signature { + color: var(--color-ts-index-signature); + } + .tsd-kind-constructor-signature { + color: var(--color-ts-constructor-signature); + } + .tsd-kind-parameter { + color: var(--color-ts-parameter); + } + .tsd-kind-type-parameter { + color: var(--color-ts-type-parameter); + } + .tsd-kind-accessor { + color: var(--color-ts-accessor); + } + .tsd-kind-get-signature { + color: var(--color-ts-get-signature); + } + .tsd-kind-set-signature { + color: var(--color-ts-set-signature); + } + .tsd-kind-type-alias { + color: var(--color-ts-type-alias); + } + + /* if we have a kind icon, don't color the text by kind */ + .tsd-kind-icon ~ span { + color: var(--color-text); + } + + * { + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); + } + + *::-webkit-scrollbar { + width: 0.75rem; + } + + *::-webkit-scrollbar-track { + background: var(--color-icon-background); + } + + *::-webkit-scrollbar-thumb { + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); + } + + /* mobile */ + @media (max-width: 769px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } + + .container-main { + display: flex; + } + html .col-content { + float: none; + max-width: 100%; + width: 100%; + } + html .col-sidebar { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + width: 75vw; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-sidebar > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu .col-sidebar { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu .col-sidebar { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu .col-sidebar { + visibility: visible; + transform: translate(0, 0); + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } + } + + /* one sidebar */ + @media (min-width: 770px) { + .container-main { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + grid-template-areas: "sidebar content"; + margin: 2rem auto; + } + + .col-sidebar { + grid-area: sidebar; + } + .col-content { + grid-area: content; + padding: 0 1rem; + } + } + @media (min-width: 770px) and (max-width: 1399px) { + .col-sidebar { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + padding-top: 1rem; + } + .site-menu { + margin-top: 1rem; + } + } + + /* two sidebars */ + @media (min-width: 1200px) { + .container-main { + grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax( + 0, + 20rem + ); + grid-template-areas: "sidebar content toc"; + } + + .col-sidebar { + display: contents; + } + + .page-menu { + grid-area: toc; + padding-left: 1rem; + } + .site-menu { + grid-area: sidebar; + } + + .site-menu { + margin-top: 1rem; + } + + .page-menu, + .site-menu { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html new file mode 100644 index 000000000..c4e9ce71d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/classes/Call.RuntimeError.html @@ -0,0 +1,11 @@ +RuntimeError | @wailsio/runtime

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

+

Hierarchy

Constructors

Properties

Constructors

  • Constructs a new RuntimeError instance.

    +

    Parameters

    • Optionalmessage: string

      The error message.

      +
    • Optionaloptions: ErrorOptions

      Options to be forwarded to the Error constructor.

      +

    Returns RuntimeError

Properties

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

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

+

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

+

Hierarchy

Constructors

Properties

Constructors

Properties

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

Class CancellablePromise<T>

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

+

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

+

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

+

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

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

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

+

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

+

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

+

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

+

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

+

Type Parameters

  • T

Hierarchy

Implements

Constructors

  • Creates a new CancellablePromise.

    +

    Type Parameters

    • T

    Parameters

    • executor: CancellablePromiseExecutor<T>

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

      +
    • Optionaloncancelled: CancellablePromiseCanceller

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

      +

    Returns CancellablePromise<T>

Properties

"[toStringTag]": string
"[species]": PromiseConstructor

Methods

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

    +

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

    +

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

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

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

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

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

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

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

    +

    Parameters

    • Optionalcause: any

    Returns CancellablePromise<void>

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

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

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

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

    +

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

    +

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

    +

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

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

    instead of:

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

    Parameters

    Returns CancellablePromise<T>

    This promise, for method chaining.

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

    +

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

    +

    It is equivalent to

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

    and the same caveats apply.

    +

    Type Parameters

    • TResult = never

    Parameters

    Returns CancellablePromise<T | TResult>

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

    +

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

    +

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

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

    +

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

    +

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

    +

    Parameters

    Returns CancellablePromise<T>

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

    +

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

    +

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

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

    +

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

    +

    Type Parameters

    • TResult1 = T
    • TResult2 = never

    Parameters

    Returns CancellablePromise<TResult1 | TResult2>

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

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

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

    +

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

    +

Static Methods

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

    +

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

    +

    Type Parameters

    • T

    Parameters

    Returns CancellablePromise<Awaited<T>[]>

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

    +

    Type Parameters

    • T extends [] | readonly unknown[]

    Parameters

    • values: T

      An array of Promises.

      +

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

    A new Promise.

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

    +

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

    +

    Type Parameters

    • T

    Parameters

    Returns CancellablePromise<PromiseSettledResult<Awaited<T>>[]>

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

    +

    Type Parameters

    • T extends [] | readonly unknown[]

    Parameters

    • values: T

      An array of Promises.

      +

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

    A new Promise.

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

    +

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

    +

    Type Parameters

    • T

    Parameters

    Returns CancellablePromise<Awaited<T>>

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

    +

    Type Parameters

    • T extends [] | readonly unknown[]

    Parameters

    • values: T

      An array or iterable of Promises.

      +

    Returns CancellablePromise<Awaited<T[number]>>

    A new Promise.

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

    +

    Parameters

    • milliseconds: number

    Returns CancellablePromise<void>

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

    +

    Type Parameters

    • T

    Parameters

    • milliseconds: number
    • value: T

    Returns CancellablePromise<T>

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

    +

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

    +

    Type Parameters

    • T = never

    Parameters

    • milliseconds: number
    • Optionalcause: any

    Returns CancellablePromise<T>

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

Class CancelledRejectionError

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

+

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

+

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

+

Hierarchy

  • Error
    • CancelledRejectionError

Constructors

Properties

Constructors

  • Constructs a new CancelledRejectionError instance.

    +

    Parameters

    • promise: CancellablePromise<unknown>

      The promise that caused the error originally.

      +
    • Optionalreason: any

      The rejection reason.

      +
    • Optionalinfo: string

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

      +

    Returns CancelledRejectionError

Properties

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

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

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

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

+

Constructors

Properties

Constructors

Properties

data: any

Optional data associated with the emitted event.

+
name: string

The name of the event.

+
sender?: string

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

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

Methods

  • Gets the specified window.

    +

    Parameters

    • name: string

      The name of the window to get.

      +

    Returns Window

    The corresponding window object.

    +
  • Returns true if the window is focused.

    +

    Returns Promise<boolean>

    Whether the window is currently focused.

    +
  • Returns true if the window is fullscreen.

    +

    Returns Promise<boolean>

    Whether the window is currently fullscreen.

    +
  • Returns true if the window is maximised.

    +

    Returns Promise<boolean>

    Whether the window is currently maximised.

    +
  • Returns true if the window is minimised.

    +

    Returns Promise<boolean>

    Whether the window is currently minimised.

    +
  • Returns true if the window is resizable.

    +

    Returns Promise<boolean>

    Whether the window is currently resizable.

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

    +

    Returns Promise<void>

  • Sets the window to be always on top.

    +

    Parameters

    • alwaysOnTop: boolean

      Whether the window should stay on top.

      +

    Returns Promise<void>

  • Sets the background colour of the window.

    +

    Parameters

    • r: number

      The desired red component of the window background.

      +
    • g: number

      The desired green component of the window background.

      +
    • b: number

      The desired blue component of the window background.

      +
    • a: number

      The desired alpha component of the window background.

      +

    Returns Promise<void>

  • Removes the window frame and title bar.

    +

    Parameters

    • frameless: boolean

      Whether the window should be frameless.

      +

    Returns Promise<void>

  • Disables the system fullscreen button.

    +

    Parameters

    • enabled: boolean

      Whether the fullscreen button should be enabled.

      +

    Returns Promise<void>

  • Sets the maximum size of the window.

    +

    Parameters

    • width: number

      The desired maximum width of the window.

      +
    • height: number

      The desired maximum height of the window.

      +

    Returns Promise<void>

  • Sets the minimum size of the window.

    +

    Parameters

    • width: number

      The desired minimum width of the window.

      +
    • height: number

      The desired minimum height of the window.

      +

    Returns Promise<void>

  • Sets the absolute position of the window.

    +

    Parameters

    • x: number

      The desired horizontal absolute position of the window.

      +
    • y: number

      The desired vertical absolute position of the window.

      +

    Returns Promise<void>

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

    +

    Parameters

    • x: number

      The desired horizontal relative position of the window.

      +
    • y: number

      The desired vertical relative position of the window.

      +

    Returns Promise<void>

  • Sets whether the window is resizable.

    +

    Parameters

    • resizable: boolean

      Whether the window should be resizable.

      +

    Returns Promise<void>

  • Sets the size of the window.

    +

    Parameters

    • width: number

      The desired width of the window.

      +
    • height: number

      The desired height of the window.

      +

    Returns Promise<void>

  • Sets the title of the window.

    +

    Parameters

    • title: string

      The desired title of the window.

      +

    Returns Promise<void>

  • Sets the zoom level of the window.

    +

    Parameters

    • zoom: number

      The desired zoom level.

      +

    Returns Promise<void>

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

    +

    Parameters

    • methodID: number

      The ID of the method to call.

      +
    • ...args: any[]

      The arguments to pass to the method.

      +

    Returns CancellablePromise<any>

    The result of the method call.

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

    +

    Parameters

    • methodName: string

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

      +
    • ...args: any[]

      The arguments to pass to the method.

      +

    Returns CancellablePromise<any>

    The result of the method call.

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

    +

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

    +

    Parameters

    Returns CancellablePromise<any>

    The result of the call.

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

    +

    Parameters

    • text: string

      The text to be set to the Clipboard.

      +

    Returns Promise<void>

    A Promise that resolves when the operation is successful.

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

    +

    Parameters

    Returns Promise<string[]>

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

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

    +

    Parameters

    Returns Promise<string>

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

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

    +

    Parameters

    Returns Promise<string | string[]>

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

    +
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html new file mode 100644 index 000000000..cb7ad090a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Question.html @@ -0,0 +1,4 @@ +Question | @wailsio/runtime
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html new file mode 100644 index 000000000..7e2ec1103 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.SaveFile.html @@ -0,0 +1,4 @@ +SaveFile | @wailsio/runtime
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html new file mode 100644 index 000000000..be1da407a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Dialogs.Warning.html @@ -0,0 +1,4 @@ +Warning | @wailsio/runtime
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html new file mode 100644 index 000000000..472ec4758 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/functions/Events.Emit.html @@ -0,0 +1,5 @@ +Emit | @wailsio/runtime
  • Emits an event using the name and data.

    +

    Parameters

    • name: string

      the name of the event to emit.

      +
    • Optionaldata: any

      the data to be sent with the event.

      +

    Returns Promise<void>

    A promise that will be fulfilled once the event has been emitted.

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

    +

    Parameters

    • ...eventNames: [string, ...string[]]

      The name of the events to remove listeners for.

      +

    Returns void

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

    +

    Parameters

    • eventName: string

      The name of the event to register the callback for.

      +
    • callback: Callback

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

      +

    Returns () => void

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

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

    +

    Parameters

    • eventName: string

      The name of the event to register the callback for.

      +
    • callback: Callback

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

      +
    • maxCallbacks: number

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

      +

    Returns () => void

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

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

    +

    Parameters

    • eventName: string

      The name of the event to register the callback for.

      +
    • callback: Callback

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

      +

    Returns () => void

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

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

    +

    Parameters

    • key: string

      The key to retrieve the value for.

      +

    Returns any

    The value associated with the specified key.

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

    +

    Returns Promise<Record<string, any>>

    A promise that resolves to an object containing the capabilities.

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

    +

    Returns boolean

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

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

    +

    Returns boolean

    True if the current architecture is ARM, false otherwise.

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

    +

    Returns boolean

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

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

    +

    Returns Promise<boolean>

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

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

    +

    Returns boolean

    True if the app is being run in debug mode.

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

    +

    Returns boolean

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

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

    +

    Returns boolean

    True if the environment is macOS, false otherwise.

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

    +

    Returns boolean

    True if the operating system is Windows, otherwise false.

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

    +

    Returns void

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

    +

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html new file mode 100644 index 000000000..298304741 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html @@ -0,0 +1 @@ +@wailsio/runtime
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html new file mode 100644 index 000000000..90f345405 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/index.html @@ -0,0 +1,7 @@ +@wailsio/runtime

@wailsio/runtime

README

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

+

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

+

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

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

Interface CancellablePromiseLike<T>

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

Type Parameters

  • T

Implemented by

Methods

Methods

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

Interface CancellablePromiseWithResolvers<T>

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

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

Type Parameters

  • T

Properties

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

Properties

IsCancel?: boolean

True if the button should cancel an operation when clicked.

+
IsDefault?: boolean

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

+
Label?: string

Text that appears within the button.

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

Properties

Properties

DisplayName?: string

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

+
Pattern?: string

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

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

Interface MessageDialogOptions

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

Properties

Buttons?: Button[]

Array of button options to show in the dialog.

+
Detached?: boolean

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

+
Message?: string

The main message to show in the dialog.

+
Title?: string

The title of the dialog window.

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

Interface OpenFileDialogOptions

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

Properties

AllowsMultipleSelection?: boolean

Indicates if multiple selection is allowed.

+
AllowsOtherFiletypes?: boolean

Indicates if other file types are allowed.

+
ButtonText?: string

Text to display on the button.

+
CanChooseDirectories?: boolean

Indicates if directories can be chosen.

+
CanChooseFiles?: boolean

Indicates if files can be chosen.

+
CanCreateDirectories?: boolean

Indicates if directories can be created.

+
CanSelectHiddenExtension?: boolean

Indicates if hidden extensions can be selected.

+
Detached?: boolean

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

+
Directory?: string

Directory to open in the dialog.

+
Filters?: FileFilter[]

Array of file filters.

+
HideExtension?: boolean

Indicates if the extension should be hidden.

+
Message?: string

Message to show in the dialog.

+
ResolvesAliases?: boolean

Indicates if aliases should be resolved.

+
ShowHiddenFiles?: boolean

Indicates if hidden files should be shown.

+
Title?: string

Title of the dialog.

+
TreatsFilePackagesAsDirectories?: boolean

Indicates if file packages should be treated as directories.

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

Interface SaveFileDialogOptions

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

Properties

AllowsOtherFiletypes?: boolean

Indicates if other file types are allowed.

+
ButtonText?: string

Text to display on the button.

+
CanChooseDirectories?: boolean

Indicates if directories can be chosen.

+
CanChooseFiles?: boolean

Indicates if files can be chosen.

+
CanCreateDirectories?: boolean

Indicates if directories can be created.

+
CanSelectHiddenExtension?: boolean

Indicates if hidden extensions can be selected.

+
Detached?: boolean

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

+
Directory?: string

Directory to open in the dialog.

+
Filename?: string

Default filename to use in the dialog.

+
Filters?: FileFilter[]

Array of file filters.

+
HideExtension?: boolean

Indicates if the extension should be hidden.

+
Message?: string

Message to show in the dialog.

+
ResolvesAliases?: boolean

Indicates if aliases should be resolved.

+
ShowHiddenFiles?: boolean

Indicates if hidden files should be shown.

+
Title?: string

Title of the dialog.

+
TreatsFilePackagesAsDirectories?: boolean

Indicates if file packages should be treated as directories.

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

Properties

Properties

Height: number

The height of the rectangle.

+
Width: number

The width of the rectangle.

+
X: number

The X coordinate of the origin.

+
Y: number

The Y coordinate of the origin.

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

Properties

Bounds: Rect

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

+
ID: string

Unique identifier for the screen.

+
IsPrimary: boolean

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

+
Name: string

Human-readable name of the screen.

+
PhysicalBounds: Rect

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

+
PhysicalWorkArea: Rect

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

+
Rotation: number

The rotation of the screen.

+
ScaleFactor: number

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

+

Contains the width and height of the screen.

+
WorkArea: Rect

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

+
X: number

The X coordinate of the screen.

+
Y: number

The Y coordinate of the screen.

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

Properties

Properties

Height: number

The height of a rectangular area.

+
Width: number

The width of a rectangular area.

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

Interface EnvironmentInfo

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

Properties

Properties

Arch: string

The architecture of the system.

+
Debug: boolean

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

+
OS: string

The operating system in use.

+
OSInfo: OSInfo

Details of the operating system.

+
PlatformInfo: Record<string, any>

Additional platform information.

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

Properties

Properties

Branding: string

The branding of the OS.

+
ID: string

The ID of the OS.

+
Name: string

The name of the OS.

+
Version: string

The version of the OS.

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

Interface AddEventListenerOptions

interface AddEventListenerOptions {
ย ย ย ย capture?: boolean;
ย ย ย ย once?: boolean;
ย ย ย ย passive?: boolean;
ย ย ย ย signal?: AbortSignal;
}

Hierarchy (View Summary)

Properties

capture?: boolean
once?: boolean
passive?: boolean
signal?: AbortSignal
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ArrayBufferView.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ArrayBufferView.html new file mode 100644 index 000000000..34cc95d72 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ArrayBufferView.html @@ -0,0 +1,7 @@ +ArrayBufferView | @wailsio/runtime

Interface ArrayBufferView<TArrayBuffer>

interface ArrayBufferView<
ย ย ย ย TArrayBuffer extends ArrayBufferLike = ArrayBufferLike,
> {
ย ย ย ย buffer: TArrayBuffer;
ย ย ย ย byteLength: number;
ย ย ย ย byteOffset: number;
}

Type Parameters

Properties

buffer: TArrayBuffer

The ArrayBuffer instance referenced by the array.

+
byteLength: number

The length in bytes of the array.

+
byteOffset: number

The offset in bytes of the array.

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

A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.

+

MDN Reference

+
interface Blob {
ย ย ย ย size: number;
ย ย ย ย type: string;
ย ย ย ย arrayBuffer(): Promise<ArrayBuffer>;
ย ย ย ย bytes(): Promise<Uint8Array<ArrayBufferLike>>;
ย ย ย ย slice(start?: number, end?: number, contentType?: string): Blob;
ย ย ย ย stream(): ReadableStream<Uint8Array<ArrayBufferLike>>;
ย ย ย ย text(): Promise<string>;
}

Properties

Methods

Properties

size: number
type: string

Methods

  • Parameters

    • Optionalstart: number
    • Optionalend: number
    • OptionalcontentType: string

    Returns Blob

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.BlobPropertyBag.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.BlobPropertyBag.html new file mode 100644 index 000000000..6fdfdc2d7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.BlobPropertyBag.html @@ -0,0 +1,3 @@ +BlobPropertyBag | @wailsio/runtime
interface BlobPropertyBag {
ย ย ย ย endings?: EndingType;
ย ย ย ย type?: string;
}

Properties

Properties

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

Properties

Properties

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

An event which takes place in the DOM.

+

MDN Reference

+
interface Event {
ย ย ย ย AT_TARGET: 2;
ย ย ย ย bubbles: boolean;
ย ย ย ย BUBBLING_PHASE: 3;
ย ย ย ย cancelable: boolean;
ย ย ย ย cancelBubble: boolean;
ย ย ย ย CAPTURING_PHASE: 1;
ย ย ย ย composed: boolean;
ย ย ย ย currentTarget: null | EventTarget;
ย ย ย ย defaultPrevented: boolean;
ย ย ย ย eventPhase: number;
ย ย ย ย isTrusted: boolean;
ย ย ย ย NONE: 0;
ย ย ย ย returnValue: boolean;
ย ย ย ย srcElement: null | EventTarget;
ย ย ย ย target: null | EventTarget;
ย ย ย ย timeStamp: number;
ย ย ย ย type: string;
ย ย ย ย composedPath(): EventTarget[];
ย ย ย ย initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void;
ย ย ย ย preventDefault(): void;
ย ย ย ย stopImmediatePropagation(): void;
ย ย ย ย stopPropagation(): void;
}

Properties

AT_TARGET: 2
bubbles: boolean

Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise.

+

MDN Reference

+
BUBBLING_PHASE: 3
cancelable: boolean

Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method.

+

MDN Reference

+
cancelBubble: boolean

MDN Reference

+
CAPTURING_PHASE: 1
composed: boolean

Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise.

+

MDN Reference

+
currentTarget: null | EventTarget

Returns the object whose event listener's callback is currently being invoked.

+

MDN Reference

+
defaultPrevented: boolean

Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise.

+

MDN Reference

+
eventPhase: number

Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE.

+

MDN Reference

+
isTrusted: boolean

Returns true if event was dispatched by the user agent, and false otherwise.

+

MDN Reference

+
NONE: 0
returnValue: boolean

MDN Reference

+
srcElement: null | EventTarget

MDN Reference

+
target: null | EventTarget

Returns the object to which event is dispatched (its target).

+

MDN Reference

+
timeStamp: number

Returns the event's timestamp as the number of milliseconds measured relative to the time origin.

+

MDN Reference

+
type: string

Returns the type of event, e.g. "click", "hashchange", or "submit".

+

MDN Reference

+

Methods

  • Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget.

    +

    MDN Reference

    +

    Returns EventTarget[]

  • Parameters

    • type: string
    • Optionalbubbles: boolean
    • Optionalcancelable: boolean

    Returns void

    MDN Reference

    +
  • If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled.

    +

    MDN Reference

    +

    Returns void

  • Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects.

    +

    MDN Reference

    +

    Returns void

  • When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object.

    +

    MDN Reference

    +

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventInit.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventInit.html new file mode 100644 index 000000000..27cc94ba8 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventInit.html @@ -0,0 +1,4 @@ +EventInit | @wailsio/runtime
interface EventInit {
ย ย ย ย bubbles?: boolean;
ย ย ย ย cancelable?: boolean;
ย ย ย ย composed?: boolean;
}

Properties

bubbles?: boolean
cancelable?: boolean
composed?: boolean
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListener.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListener.html new file mode 100644 index 000000000..aa2f55376 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListener.html @@ -0,0 +1 @@ +EventListener | @wailsio/runtime
  • Parameters

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerObject.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerObject.html new file mode 100644 index 000000000..c644929fd --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerObject.html @@ -0,0 +1,2 @@ +EventListenerObject | @wailsio/runtime
interface EventListenerObject {
ย ย ย ย handleEvent(object: Event): void;
}

Methods

Methods

  • Parameters

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerOptions.html new file mode 100644 index 000000000..5aa0f9a40 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventListenerOptions.html @@ -0,0 +1,2 @@ +EventListenerOptions | @wailsio/runtime
interface EventListenerOptions {
ย ย ย ย capture?: boolean;
}

Hierarchy (View Summary)

Properties

Properties

capture?: boolean
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventTarget.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventTarget.html new file mode 100644 index 000000000..949e7f417 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.EventTarget.html @@ -0,0 +1,18 @@ +EventTarget | @wailsio/runtime

EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them.

+

MDN Reference

+
interface EventTarget {
ย ย ย ย addEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย callback: null | EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | AddEventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย dispatchEvent(event: Event): boolean;
ย ย ย ย removeEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย callback: null | EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | EventListenerOptions,
ย ย ย ย ): void;
}

Hierarchy (View Summary)

Methods

  • Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.

    +

    The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.

    +

    When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.

    +

    When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in ยง 2.8 Observing event listeners.

    +

    When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.

    +

    If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.

    +

    The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.

    +

    MDN Reference

    +

    Parameters

    Returns void

  • Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.

    +

    MDN Reference

    +

    Parameters

    Returns boolean

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

Interface Iterable<T, TReturn, TNext>

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

Type Parameters

  • T
  • TReturn = any
  • TNext = any

Methods

Methods

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

This Media Source Extensions API interface represents a source of media data for an HTMLMediaElement object. A MediaSource object can be attached to a HTMLMediaElement to be played in the user agent.

+

MDN Reference

+
interface MediaSource {
ย ย ย ย activeSourceBuffers: SourceBufferList;
ย ย ย ย duration: number;
ย ย ย ย onsourceclose: null | (this: MediaSource, ev: Event) => any;
ย ย ย ย onsourceended: null | (this: MediaSource, ev: Event) => any;
ย ย ย ย onsourceopen: null | (this: MediaSource, ev: Event) => any;
ย ย ย ย readyState: ReadyState;
ย ย ย ย sourceBuffers: SourceBufferList;
ย ย ย ย addEventListener<K extends keyof MediaSourceEventMap>(
ย ย ย ย ย ย ย ย type: K,
ย ย ย ย ย ย ย ย listener: (this: MediaSource, ev: MediaSourceEventMap[K]) => any,
ย ย ย ย ย ย ย ย options?: boolean | AddEventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย addEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย listener: EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | AddEventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย addSourceBuffer(type: string): SourceBuffer;
ย ย ย ย clearLiveSeekableRange(): void;
ย ย ย ย dispatchEvent(event: Event): boolean;
ย ย ย ย endOfStream(error?: EndOfStreamError): void;
ย ย ย ย removeEventListener<K extends keyof MediaSourceEventMap>(
ย ย ย ย ย ย ย ย type: K,
ย ย ย ย ย ย ย ย listener: (this: MediaSource, ev: MediaSourceEventMap[K]) => any,
ย ย ย ย ย ย ย ย options?: boolean | EventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย removeEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย listener: EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | EventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย removeSourceBuffer(sourceBuffer: SourceBuffer): void;
ย ย ย ย setLiveSeekableRange(start: number, end: number): void;
}

Hierarchy (View Summary)

Properties

activeSourceBuffers: SourceBufferList
duration: number
onsourceclose: null | (this: MediaSource, ev: Event) => any
onsourceended: null | (this: MediaSource, ev: Event) => any
onsourceopen: null | (this: MediaSource, ev: Event) => any
readyState: ReadyState
sourceBuffers: SourceBufferList

Methods

  • Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.

    +

    The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.

    +

    When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.

    +

    When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in ยง 2.8 Observing event listeners.

    +

    When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.

    +

    If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.

    +

    The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.

    +

    MDN Reference

    +

    Type Parameters

    Parameters

    Returns void

  • Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.

    +

    The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.

    +

    When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.

    +

    When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in ยง 2.8 Observing event listeners.

    +

    When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.

    +

    If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.

    +

    The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.

    +

    MDN Reference

    +

    Parameters

    Returns void

  • Returns void

  • Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.

    +

    MDN Reference

    +

    Parameters

    Returns boolean

  • Parameters

    • start: number
    • end: number

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.MediaSourceEventMap.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.MediaSourceEventMap.html new file mode 100644 index 000000000..ece981957 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.MediaSourceEventMap.html @@ -0,0 +1,4 @@ +MediaSourceEventMap | @wailsio/runtime
interface MediaSourceEventMap {
ย ย ย ย sourceclose: Event;
ย ย ย ย sourceended: Event;
ย ย ย ย sourceopen: Event;
}

Properties

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

A record describing the position of a window.

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

Properties

x +y +

Properties

x: number

The horizontal position of the window.

+
y: number

The vertical position of the window.

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

Interface PromiseFulfilledResult<T>

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

Type Parameters

  • T

Properties

Properties

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

Type Parameters

  • T

Implemented by

Methods

Methods

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

Properties

Properties

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

Interface PromiseWithResolvers<T>

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

Type Parameters

  • T

Properties

Properties

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

Interface QueuingStrategy<T>

interface QueuingStrategy<T = any> {
ย ย ย ย highWaterMark?: number;
ย ย ย ย size?: QueuingStrategySize<T>;
}

Type Parameters

  • T = any

Properties

Properties

highWaterMark?: number
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.QueuingStrategySize.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.QueuingStrategySize.html new file mode 100644 index 000000000..5f653c14e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.QueuingStrategySize.html @@ -0,0 +1 @@ +QueuingStrategySize | @wailsio/runtime

Interface QueuingStrategySize<T>

Type Parameters

  • T = any
  • Parameters

    • chunk: T

    Returns number

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

Interface ReadableByteStreamController

interface ReadableByteStreamController {
ย ย ย ย byobRequest: null | ReadableStreamBYOBRequest;
ย ย ย ย desiredSize: null | number;
ย ย ย ย close(): void;
ย ย ย ย enqueue(chunk: ArrayBufferView): void;
ย ย ย ย error(e?: any): void;
}

Properties

Methods

Properties

byobRequest: null | ReadableStreamBYOBRequest
desiredSize: null | number

Methods

  • Returns void

  • Parameters

    • Optionale: any

    Returns void

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

Interface ReadableStream<R>

This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object.

+

MDN Reference

+
interface ReadableStream<R = any> {
ย ย ย ย locked: boolean;
ย ย ย ย cancel(reason?: any): Promise<void>;
ย ย ย ย getReader(options: { mode: "byob" }): ReadableStreamBYOBReader;
ย ย ย ย getReader(): ReadableStreamDefaultReader<R>;
ย ย ย ย getReader(
ย ย ย ย ย ย ย ย options?: ReadableStreamGetReaderOptions,
ย ย ย ย ): ReadableStreamReader<R>;
ย ย ย ย pipeThrough<T>(
ย ย ย ย ย ย ย ย transform: ReadableWritablePair<T, R>,
ย ย ย ย ย ย ย ย options?: StreamPipeOptions,
ย ย ย ย ): ReadableStream<T>;
ย ย ย ย pipeTo(
ย ย ย ย ย ย ย ย destination: WritableStream<R>,
ย ย ย ย ย ย ย ย options?: StreamPipeOptions,
ย ย ย ย ): Promise<void>;
ย ย ย ย tee(): [ReadableStream<R>, ReadableStream<R>];
}

Type Parameters

  • R = any

Properties

Methods

Properties

locked: boolean

Methods

  • Parameters

    • Optionalreason: any

    Returns Promise<void>

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

Interface ReadableStreamBYOBReader

interface ReadableStreamBYOBReader {
ย ย ย ย closed: Promise<undefined>;
ย ย ย ย cancel(reason?: any): Promise<void>;
ย ย ย ย read<T extends ArrayBufferView<ArrayBufferLike>>(
ย ย ย ย ย ย ย ย view: T,
ย ย ย ย ): Promise<ReadableStreamReadResult<T>>;
ย ย ย ย releaseLock(): void;
}

Hierarchy (View Summary)

Properties

Methods

Properties

closed: Promise<undefined>

Methods

  • Returns void

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

Interface ReadableStreamBYOBRequest

interface ReadableStreamBYOBRequest {
ย ย ย ย view: null | ArrayBufferView<ArrayBufferLike>;
ย ย ย ย respond(bytesWritten: number): void;
ย ย ย ย respondWithNewView(view: ArrayBufferView): void;
}

Properties

Methods

Properties

Methods

  • Parameters

    • bytesWritten: number

    Returns void

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

Interface ReadableStreamDefaultController<R>

interface ReadableStreamDefaultController<R = any> {
ย ย ย ย desiredSize: null | number;
ย ย ย ย close(): void;
ย ย ย ย enqueue(chunk?: R): void;
ย ย ย ย error(e?: any): void;
}

Type Parameters

  • R = any

Properties

Methods

Properties

desiredSize: null | number

Methods

  • Returns void

  • Parameters

    • Optionalchunk: R

    Returns void

  • Parameters

    • Optionale: any

    Returns void

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

Interface ReadableStreamDefaultReader<R>

interface ReadableStreamDefaultReader<R = any> {
ย ย ย ย closed: Promise<undefined>;
ย ย ย ย cancel(reason?: any): Promise<void>;
ย ย ย ย read(): Promise<ReadableStreamReadResult<R>>;
ย ย ย ย releaseLock(): void;
}

Type Parameters

  • R = any

Hierarchy (View Summary)

Properties

Methods

Properties

closed: Promise<undefined>

Methods

  • Returns void

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

Interface ReadableStreamGenericReader

interface ReadableStreamGenericReader {
ย ย ย ย closed: Promise<undefined>;
ย ย ย ย cancel(reason?: any): Promise<void>;
}

Hierarchy (View Summary)

Properties

Methods

Properties

closed: Promise<undefined>

Methods

  • Parameters

    • Optionalreason: any

    Returns Promise<void>

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

Interface ReadableStreamGetReaderOptions

interface ReadableStreamGetReaderOptions {
ย ย ย ย mode?: "byob";
}

Properties

Properties

mode?: "byob"

Creates a ReadableStreamBYOBReader and locks the stream to the new reader.

+

This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.

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

Interface ReadableStreamReadDoneResult<T>

interface ReadableStreamReadDoneResult<T> {
ย ย ย ย done: true;
ย ย ย ย value?: T;
}

Type Parameters

  • T

Properties

Properties

done: true
value?: T
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableStreamReadValueResult.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableStreamReadValueResult.html new file mode 100644 index 000000000..a9b44ff4e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableStreamReadValueResult.html @@ -0,0 +1,3 @@ +ReadableStreamReadValueResult | @wailsio/runtime

Interface ReadableStreamReadValueResult<T>

interface ReadableStreamReadValueResult<T> {
ย ย ย ย done: false;
ย ย ย ย value: T;
}

Type Parameters

  • T

Properties

Properties

done: false
value: T
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableWritablePair.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableWritablePair.html new file mode 100644 index 000000000..33668f35b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.ReadableWritablePair.html @@ -0,0 +1,5 @@ +ReadableWritablePair | @wailsio/runtime

Interface ReadableWritablePair<R, W>

interface ReadableWritablePair<R = any, W = any> {
ย ย ย ย readable: ReadableStream<R>;
ย ย ย ย writable: WritableStream<W>;
}

Type Parameters

  • R = any
  • W = any

Properties

Properties

readable: ReadableStream<R>
writable: WritableStream<W>

Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.

+

Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.

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

A record describing the size of a window.

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

Properties

Properties

height: number

The height of the window.

+
width: number

The width of the window.

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

A chunk of media to be passed into an HTMLMediaElement and played, via a MediaSourceย object. This can be made up of one or several media segments.

+

MDN Reference

+
interface SourceBuffer {
ย ย ย ย appendWindowEnd: number;
ย ย ย ย appendWindowStart: number;
ย ย ย ย buffered: TimeRanges;
ย ย ย ย mode: AppendMode;
ย ย ย ย onabort: null | (this: SourceBuffer, ev: Event) => any;
ย ย ย ย onerror: null | (this: SourceBuffer, ev: Event) => any;
ย ย ย ย onupdate: null | (this: SourceBuffer, ev: Event) => any;
ย ย ย ย onupdateend: null | (this: SourceBuffer, ev: Event) => any;
ย ย ย ย onupdatestart: null | (this: SourceBuffer, ev: Event) => any;
ย ย ย ย timestampOffset: number;
ย ย ย ย updating: boolean;
ย ย ย ย abort(): void;
ย ย ย ย addEventListener<K extends keyof SourceBufferEventMap>(
ย ย ย ย ย ย ย ย type: K,
ย ย ย ย ย ย ย ย listener: (this: SourceBuffer, ev: SourceBufferEventMap[K]) => any,
ย ย ย ย ย ย ย ย options?: boolean | AddEventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย addEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย listener: EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | AddEventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย appendBuffer(data: BufferSource): void;
ย ย ย ย changeType(type: string): void;
ย ย ย ย dispatchEvent(event: Event): boolean;
ย ย ย ย remove(start: number, end: number): void;
ย ย ย ย removeEventListener<K extends keyof SourceBufferEventMap>(
ย ย ย ย ย ย ย ย type: K,
ย ย ย ย ย ย ย ย listener: (this: SourceBuffer, ev: SourceBufferEventMap[K]) => any,
ย ย ย ย ย ย ย ย options?: boolean | EventListenerOptions,
ย ย ย ย ): void;
ย ย ย ย removeEventListener(
ย ย ย ย ย ย ย ย type: string,
ย ย ย ย ย ย ย ย listener: EventListenerOrEventListenerObject,
ย ย ย ย ย ย ย ย options?: boolean | EventListenerOptions,
ย ย ย ย ): void;
}

Hierarchy (View Summary)

Properties

appendWindowEnd: number
appendWindowStart: number
buffered: TimeRanges
onabort: null | (this: SourceBuffer, ev: Event) => any
onerror: null | (this: SourceBuffer, ev: Event) => any
onupdate: null | (this: SourceBuffer, ev: Event) => any
onupdateend: null | (this: SourceBuffer, ev: Event) => any
onupdatestart: null | (this: SourceBuffer, ev: Event) => any
timestampOffset: number
updating: boolean

Methods

  • Returns void

  • Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.

    +

    The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.

    +

    When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.

    +

    When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in ยง 2.8 Observing event listeners.

    +

    When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.

    +

    If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.

    +

    The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.

    +

    MDN Reference

    +

    Type Parameters

    Parameters

    Returns void

  • Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.

    +

    The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.

    +

    When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.

    +

    When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in ยง 2.8 Observing event listeners.

    +

    When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.

    +

    If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.

    +

    The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.

    +

    MDN Reference

    +

    Parameters

    Returns void

  • Parameters

    • type: string

    Returns void

  • Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.

    +

    MDN Reference

    +

    Parameters

    Returns boolean

  • Parameters

    • start: number
    • end: number

    Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.SourceBufferEventMap.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.SourceBufferEventMap.html new file mode 100644 index 000000000..95e9fee8d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.SourceBufferEventMap.html @@ -0,0 +1,6 @@ +SourceBufferEventMap | @wailsio/runtime
interface SourceBufferEventMap {
ย ย ย ย abort: Event;
ย ย ย ย error: Event;
ย ย ย ย update: Event;
ย ย ย ย updateend: Event;
ย ย ย ย updatestart: Event;
}

Properties

abort: Event
error: Event
update: Event
updateend: Event
updatestart: Event
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.StreamPipeOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.StreamPipeOptions.html new file mode 100644 index 000000000..ec92b448b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.StreamPipeOptions.html @@ -0,0 +1,13 @@ +StreamPipeOptions | @wailsio/runtime
interface StreamPipeOptions {
ย ย ย ย preventAbort?: boolean;
ย ย ย ย preventCancel?: boolean;
ย ย ย ย preventClose?: boolean;
ย ย ย ย signal?: AbortSignal;
}

Properties

preventAbort?: boolean
preventCancel?: boolean
preventClose?: boolean

Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.

+

Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.

+

Errors and closures of the source and destination streams propagate as follows:

+

An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.

+

An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.

+

When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.

+

If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.

+

The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.

+
signal?: AbortSignal
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.TimeRanges.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.TimeRanges.html new file mode 100644 index 000000000..f93dd4e77 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.TimeRanges.html @@ -0,0 +1,14 @@ +TimeRanges | @wailsio/runtime

Used to represent a set of time ranges, primarily for the purpose of tracking which portions of media have been buffered when loading it for use by the

+

MDN Reference

+
interface TimeRanges {
ย ย ย ย length: number;
ย ย ย ย end(index: number): number;
ย ย ย ย start(index: number): number;
}

Properties

Methods

Properties

length: number

Returns the number of ranges in the object.

+

MDN Reference

+

Methods

  • Returns the time for the end of the range with the given index.

    +

    Throws an "IndexSizeError" DOMException if the index is out of range.

    +

    MDN Reference

    +

    Parameters

    • index: number

    Returns number

  • Returns the time for the start of the range with the given index.

    +

    Throws an "IndexSizeError" DOMException if the index is out of range.

    +

    MDN Reference

    +

    Parameters

    • index: number

    Returns number

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

The URLย interface represents an object providing static methods used for creating object URLs.

+

MDN Reference

+
interface URL {
ย ย ย ย hash: string;
ย ย ย ย host: string;
ย ย ย ย hostname: string;
ย ย ย ย href: string;
ย ย ย ย origin: string;
ย ย ย ย password: string;
ย ย ย ย pathname: string;
ย ย ย ย port: string;
ย ย ย ย protocol: string;
ย ย ย ย search: string;
ย ย ย ย searchParams: URLSearchParams;
ย ย ย ย username: string;
ย ย ย ย toJSON(): string;
ย ย ย ย toString(): string;
}

Properties

hash: string
host: string
hostname: string
href: string
origin: string
password: string
pathname: string
port: string
protocol: string
search: string
searchParams: URLSearchParams
username: string

Methods

  • Returns string

  • Returns string

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingByteSource.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingByteSource.html new file mode 100644 index 000000000..f4d07fbf6 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingByteSource.html @@ -0,0 +1,6 @@ +UnderlyingByteSource | @wailsio/runtime
interface UnderlyingByteSource {
ย ย ย ย autoAllocateChunkSize?: number;
ย ย ย ย cancel?: UnderlyingSourceCancelCallback;
ย ย ย ย pull?: (
ย ย ย ย ย ย ย ย controller: ReadableByteStreamController,
ย ย ย ย ) => void | PromiseLike<void>;
ย ย ย ย start?: (controller: ReadableByteStreamController) => any;
ย ย ย ย type: "bytes";
}

Properties

autoAllocateChunkSize?: number
pull?: (controller: ReadableByteStreamController) => void | PromiseLike<void>
start?: (controller: ReadableByteStreamController) => any
type: "bytes"
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingDefaultSource.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingDefaultSource.html new file mode 100644 index 000000000..d282894c4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingDefaultSource.html @@ -0,0 +1,5 @@ +UnderlyingDefaultSource | @wailsio/runtime

Interface UnderlyingDefaultSource<R>

interface UnderlyingDefaultSource<R = any> {
ย ย ย ย cancel?: UnderlyingSourceCancelCallback;
ย ย ย ย pull?: (
ย ย ย ย ย ย ย ย controller: ReadableStreamDefaultController<R>,
ย ย ย ย ) => void | PromiseLike<void>;
ย ย ย ย start?: (controller: ReadableStreamDefaultController<R>) => any;
ย ย ย ย type?: undefined;
}

Type Parameters

  • R = any

Properties

Properties

pull?: (
ย ย ย ย controller: ReadableStreamDefaultController<R>,
) => void | PromiseLike<void>
start?: (controller: ReadableStreamDefaultController<R>) => any
type?: undefined
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSink.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSink.html new file mode 100644 index 000000000..d6da3c8d4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSink.html @@ -0,0 +1,6 @@ +UnderlyingSink | @wailsio/runtime

Interface UnderlyingSink<W>

interface UnderlyingSink<W = any> {
ย ย ย ย abort?: UnderlyingSinkAbortCallback;
ย ย ย ย close?: UnderlyingSinkCloseCallback;
ย ย ย ย start?: UnderlyingSinkStartCallback;
ย ย ย ย type?: undefined;
ย ย ย ย write?: UnderlyingSinkWriteCallback<W>;
}

Type Parameters

  • W = any

Properties

Properties

type?: undefined
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSinkAbortCallback.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSinkAbortCallback.html new file mode 100644 index 000000000..e45d9d445 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSinkAbortCallback.html @@ -0,0 +1 @@ +UnderlyingSinkAbortCallback | @wailsio/runtime

Interface UnderlyingSinkAbortCallback

  • Parameters

    • Optionalreason: any

    Returns void | PromiseLike<void>

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

Interface UnderlyingSinkCloseCallback

  • Returns void | PromiseLike<void>

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

Interface UnderlyingSinkStartCallback

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

Interface UnderlyingSinkWriteCallback<W>

Type Parameters

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

Interface UnderlyingSource<R>

interface UnderlyingSource<R = any> {
ย ย ย ย autoAllocateChunkSize?: number;
ย ย ย ย cancel?: UnderlyingSourceCancelCallback;
ย ย ย ย pull?: UnderlyingSourcePullCallback<R>;
ย ย ย ย start?: UnderlyingSourceStartCallback<R>;
ย ย ย ย type?: "bytes";
}

Type Parameters

  • R = any

Properties

autoAllocateChunkSize?: number
type?: "bytes"
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSourceCancelCallback.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSourceCancelCallback.html new file mode 100644 index 000000000..978b039e9 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.UnderlyingSourceCancelCallback.html @@ -0,0 +1 @@ +UnderlyingSourceCancelCallback | @wailsio/runtime

Interface UnderlyingSourceCancelCallback

  • Parameters

    • Optionalreason: any

    Returns void | PromiseLike<void>

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

Interface UnderlyingSourcePullCallback<R>

Type Parameters

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

Interface UnderlyingSourceStartCallback<R>

Type Parameters

  • R
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.WritableStream.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.WritableStream.html new file mode 100644 index 000000000..fcaeff279 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/interfaces/_internal_.WritableStream.html @@ -0,0 +1,11 @@ +WritableStream | @wailsio/runtime

Interface WritableStream<W>

This Streams API interface providesย a standard abstraction for writing streaming data to a destination, known as a sink. This object comes with built-in backpressure and queuing.

+

MDN Reference

+
interface WritableStream<W = any> {
ย ย ย ย locked: boolean;
ย ย ย ย abort(reason?: any): Promise<void>;
ย ย ย ย close(): Promise<void>;
ย ย ย ย getWriter(): WritableStreamDefaultWriter<W>;
}

Type Parameters

  • W = any

Properties

Methods

Properties

locked: boolean

Methods

  • Parameters

    • Optionalreason: any

    Returns Promise<void>

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

Interface WritableStreamDefaultController

This Streams API interface represents a controller allowing control of aย WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate.

+

MDN Reference

+
interface WritableStreamDefaultController {
ย ย ย ย signal: AbortSignal;
ย ย ย ย error(e?: any): void;
}

Properties

Methods

Properties

signal: AbortSignal

Methods

  • Parameters

    • Optionale: any

    Returns void

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

Interface WritableStreamDefaultWriter<W>

This Streams API interface is the object returned by WritableStream.getWriter() and once created locks the < writer to the WritableStream ensuring that no other streams can write to the underlying sink.

+

MDN Reference

+
interface WritableStreamDefaultWriter<W = any> {
ย ย ย ย closed: Promise<undefined>;
ย ย ย ย desiredSize: null | number;
ย ย ย ย ready: Promise<undefined>;
ย ย ย ย abort(reason?: any): Promise<void>;
ย ย ย ย close(): Promise<void>;
ย ย ย ย releaseLock(): void;
ย ย ย ย write(chunk?: W): Promise<void>;
}

Type Parameters

  • W = any

Properties

closed: Promise<undefined>
desiredSize: null | number
ready: Promise<undefined>

Methods

  • Parameters

    • Optionalreason: any

    Returns Promise<void>

  • Returns void

  • Parameters

    • Optionalchunk: W

    Returns Promise<void>

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

Namespace Application

Functions

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

Namespace Browser

Functions

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

Namespace Call

Classes

RuntimeError

Type Aliases

CallOptions

Functions

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

Namespace Clipboard

Functions

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

Namespace Events

Classes

WailsEvent

Type Aliases

Callback

Variables

Types

Functions

Emit
Off
OffAll
On
Once
OnMultiple
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html new file mode 100644 index 000000000..2004c8692 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/Flags.html @@ -0,0 +1 @@ +Flags | @wailsio/runtime

Namespace Flags

Functions

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

Namespace WML

Functions

Enable
Reload
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html new file mode 100644 index 000000000..9707760fd --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/modules/_internal_.html @@ -0,0 +1 @@ +<internal> | @wailsio/runtime

Classes

Window

Interfaces

AddEventListenerOptions
ArrayBufferView
Blob
BlobPropertyBag
ErrorOptions
Event
EventInit
EventListener
EventListenerObject
EventListenerOptions
EventTarget
Iterable
MediaSource
MediaSourceEventMap
Position
PromiseFulfilledResult
PromiseLike
PromiseRejectedResult
PromiseWithResolvers
QueuingStrategy
QueuingStrategySize
ReadableByteStreamController
ReadableStream
ReadableStreamBYOBReader
ReadableStreamBYOBRequest
ReadableStreamDefaultController
ReadableStreamDefaultReader
ReadableStreamGenericReader
ReadableStreamGetReaderOptions
ReadableStreamReadDoneResult
ReadableStreamReadValueResult
ReadableWritablePair
Size
SourceBuffer
SourceBufferEventMap
StreamPipeOptions
TimeRanges
UnderlyingByteSource
UnderlyingDefaultSource
UnderlyingSink
UnderlyingSinkAbortCallback
UnderlyingSinkCloseCallback
UnderlyingSinkStartCallback
UnderlyingSinkWriteCallback
UnderlyingSource
UnderlyingSourceCancelCallback
UnderlyingSourcePullCallback
UnderlyingSourceStartCallback
URL
WritableStream
WritableStreamDefaultController
WritableStreamDefaultWriter

Type Aliases

AppendMode
ArrayBufferLike
Awaited
BlobPart
BufferSource
CancellablePromiseCanceller
CancellablePromiseExecutor
CancellablePromiseRejector
CancellablePromiseResolver
EndingType
EndOfStreamError
EventListenerOrEventListenerObject
Partial
PromiseSettledResult
ReadableStreamController
ReadableStreamReader
ReadableStreamReadResult
Readonly
ReadyState
Record

Variables

Blob
Event
EventTarget
MediaSource
ReadableByteStreamController
ReadableStream
ReadableStreamBYOBReader
ReadableStreamBYOBRequest
ReadableStreamDefaultController
ReadableStreamDefaultReader
SourceBuffer
TimeRanges
URL
WritableStream
WritableStreamDefaultController
WritableStreamDefaultWriter
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html new file mode 100644 index 000000000..8bb2ba3c9 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Call.CallOptions.html @@ -0,0 +1,9 @@ +CallOptions | @wailsio/runtime

Type Alias CallOptions

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

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

+

Type declaration

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

      Arguments to be passed into the bound method.

      +
    • methodID: number

      The numeric ID of the bound method to call.

      +
    • OptionalmethodName?: never

      The fully qualified name of the bound method to call.

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

      Arguments to be passed into the bound method.

      +
    • OptionalmethodID?: never

      The numeric ID of the bound method to call.

      +
    • methodName: string

      The fully qualified name of the bound method to call.

      +
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.Callback.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.Callback.html new file mode 100644 index 000000000..03c56fff2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/Events.Callback.html @@ -0,0 +1,2 @@ +Callback | @wailsio/runtime
Callback: (ev: WailsEvent) => void

The type of handlers for a given event.

+

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.AppendMode.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.AppendMode.html new file mode 100644 index 000000000..21279131a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.AppendMode.html @@ -0,0 +1 @@ +AppendMode | @wailsio/runtime
AppendMode: "segments" | "sequence"
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.ArrayBufferLike.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.ArrayBufferLike.html new file mode 100644 index 000000000..27533a820 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.ArrayBufferLike.html @@ -0,0 +1 @@ +ArrayBufferLike | @wailsio/runtime
ArrayBufferLike: ArrayBufferTypes[keyof ArrayBufferTypes]
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html new file mode 100644 index 000000000..7d51c3785 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.Awaited.html @@ -0,0 +1,2 @@ +Awaited | @wailsio/runtime
Awaited: T extends null
| undefined
ย ย ย ย ? T
ย ย ย ย : T extends object & { then(onfulfilled: F, ...args: _): any }
ย ย ย ย ย ย ย ย ? F extends (value: infer V, ...args: infer _) => any
ย ย ย ย ย ย ย ย ย ย ย ย ? Awaited<V>
ย ย ย ย ย ย ย ย ย ย ย ย : never
ย ย ย ย ย ย ย ย : T

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

+

Type Parameters

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

Type Alias CancellablePromiseCanceller

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

Type declaration

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

Type Alias CancellablePromiseExecutor<T>

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

Type Parameters

  • T

Type declaration

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

Type Alias CancellablePromiseRejector

CancellablePromiseRejector: (reason?: any) => void

Type declaration

    • (reason?: any): void
    • Parameters

      • Optionalreason: any

      Returns void

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

Type Alias CancellablePromiseResolver<T>

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

Type Parameters

  • T

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndOfStreamError.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndOfStreamError.html new file mode 100644 index 000000000..df2e1e6b6 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndOfStreamError.html @@ -0,0 +1 @@ +EndOfStreamError | @wailsio/runtime
EndOfStreamError: "decode" | "network"
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndingType.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndingType.html new file mode 100644 index 000000000..97b2a0bfe --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EndingType.html @@ -0,0 +1 @@ +EndingType | @wailsio/runtime
EndingType: "native" | "transparent"
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EventListenerOrEventListenerObject.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EventListenerOrEventListenerObject.html new file mode 100644 index 000000000..c0e2bfa6d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/types/_internal_.EventListenerOrEventListenerObject.html @@ -0,0 +1 @@ +EventListenerOrEventListenerObject | @wailsio/runtime

Type Alias EventListenerOrEventListenerObject

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

Make all properties in T optional

+

Type Parameters

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

Type Alias PromiseSettledResult<T>

Type Parameters

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

Type Alias ReadableStreamController<T>

ReadableStreamController:
ย ย ย ย | ReadableStreamDefaultController<T>
ย ย ย ย | ReadableByteStreamController

Type Parameters

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

Type Alias ReadableStreamReadResult<T>

ReadableStreamReadResult:
ย ย ย ย | ReadableStreamReadValueResult<T>
ย ย ย ย | ReadableStreamReadDoneResult<T>

Type Parameters

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

Type Alias ReadableStreamReader<T>

Type Parameters

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

Make all properties in T readonly

+

Type Parameters

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

Type Alias Record<K, T>

Record: { [P in K]: T }

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

+

Type Parameters

  • K extends keyof any
  • T
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html new file mode 100644 index 000000000..c00bf128a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html @@ -0,0 +1 @@ +Types | @wailsio/runtime

Variable TypesConst

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

Variable WindowConst

Window: Window = ...

The window within which the script is running.

+
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Blob-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Blob-1.html new file mode 100644 index 000000000..7a6dca0a7 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Blob-1.html @@ -0,0 +1 @@ +Blob | @wailsio/runtime
Blob: {
ย ย ย ย prototype: Blob;
ย ย ย ย new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
}

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Event-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Event-1.html new file mode 100644 index 000000000..10a91c579 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.Event-1.html @@ -0,0 +1 @@ +Event | @wailsio/runtime
Event: {
ย ย ย ย AT_TARGET: 2;
ย ย ย ย BUBBLING_PHASE: 3;
ย ย ย ย CAPTURING_PHASE: 1;
ย ย ย ย NONE: 0;
ย ย ย ย prototype: Event;
ย ย ย ย new (type: string, eventInitDict?: EventInit): Event;
}

Type declaration

  • ReadonlyAT_TARGET: 2
  • ReadonlyBUBBLING_PHASE: 3
  • ReadonlyCAPTURING_PHASE: 1
  • ReadonlyNONE: 0
  • prototype: Event
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.EventTarget-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.EventTarget-1.html new file mode 100644 index 000000000..b260ea11e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.EventTarget-1.html @@ -0,0 +1 @@ +EventTarget | @wailsio/runtime
EventTarget: { prototype: EventTarget; new (): EventTarget }

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.MediaSource-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.MediaSource-1.html new file mode 100644 index 000000000..ff20149ac --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.MediaSource-1.html @@ -0,0 +1,3 @@ +MediaSource | @wailsio/runtime
MediaSource: {
ย ย ย ย canConstructInDedicatedWorker: boolean;
ย ย ย ย prototype: MediaSource;
ย ย ย ย isTypeSupported(type: string): boolean;
ย ย ย ย new (): MediaSource;
}

Type declaration

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

Variable ReadableByteStreamController

ReadableByteStreamController: {
ย ย ย ย prototype: ReadableByteStreamController;
ย ย ย ย new (): ReadableByteStreamController;
}
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStream-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStream-1.html new file mode 100644 index 000000000..ff895225a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStream-1.html @@ -0,0 +1 @@ +ReadableStream | @wailsio/runtime
ReadableStream: {
ย ย ย ย prototype: ReadableStream;
ย ย ย ย new (
ย ย ย ย ย ย ย ย underlyingSource: UnderlyingByteSource,
ย ย ย ย ย ย ย ย strategy?: { highWaterMark?: number },
ย ย ย ย ): ReadableStream<Uint8Array<ArrayBufferLike>>;
ย ย ย ย new <R = any>(
ย ย ย ย ย ย ย ย underlyingSource: UnderlyingDefaultSource<R>,
ย ย ย ย ย ย ย ย strategy?: QueuingStrategy<R>,
ย ย ย ย ): ReadableStream<R>;
ย ย ย ย new <R = any>(
ย ย ย ย ย ย ย ย underlyingSource?: UnderlyingSource<R>,
ย ย ย ย ย ย ย ย strategy?: QueuingStrategy<R>,
ย ย ย ย ): ReadableStream<R>;
}

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBReader-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBReader-1.html new file mode 100644 index 000000000..97d078bc2 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBReader-1.html @@ -0,0 +1 @@ +ReadableStreamBYOBReader | @wailsio/runtime
ReadableStreamBYOBReader: {
ย ย ย ย prototype: ReadableStreamBYOBReader;
ย ย ย ย new (
ย ย ย ย ย ย ย ย stream: ReadableStream<Uint8Array<ArrayBufferLike>>,
ย ย ย ย ): ReadableStreamBYOBReader;
}
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBRequest-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBRequest-1.html new file mode 100644 index 000000000..bcda606c6 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamBYOBRequest-1.html @@ -0,0 +1 @@ +ReadableStreamBYOBRequest | @wailsio/runtime

Variable ReadableStreamBYOBRequest

ReadableStreamBYOBRequest: {
ย ย ย ย prototype: ReadableStreamBYOBRequest;
ย ย ย ย new (): ReadableStreamBYOBRequest;
}
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultController-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultController-1.html new file mode 100644 index 000000000..b96ea63f8 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultController-1.html @@ -0,0 +1 @@ +ReadableStreamDefaultController | @wailsio/runtime

Variable ReadableStreamDefaultController

ReadableStreamDefaultController: {
ย ย ย ย prototype: ReadableStreamDefaultController;
ย ย ย ย new (): ReadableStreamDefaultController;
}
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultReader-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultReader-1.html new file mode 100644 index 000000000..a7d746f8d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.ReadableStreamDefaultReader-1.html @@ -0,0 +1 @@ +ReadableStreamDefaultReader | @wailsio/runtime

Variable ReadableStreamDefaultReader

ReadableStreamDefaultReader: {
ย ย ย ย prototype: ReadableStreamDefaultReader;
ย ย ย ย new <R = any>(stream: ReadableStream<R>): ReadableStreamDefaultReader<R>;
}

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.SourceBuffer-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.SourceBuffer-1.html new file mode 100644 index 000000000..ca7e84af9 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.SourceBuffer-1.html @@ -0,0 +1 @@ +SourceBuffer | @wailsio/runtime
SourceBuffer: { prototype: SourceBuffer; new (): SourceBuffer }

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.TimeRanges-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.TimeRanges-1.html new file mode 100644 index 000000000..9d9e6fa1b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.TimeRanges-1.html @@ -0,0 +1 @@ +TimeRanges | @wailsio/runtime
TimeRanges: { prototype: TimeRanges; new (): TimeRanges }

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.URL-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.URL-1.html new file mode 100644 index 000000000..a0f9adf7d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.URL-1.html @@ -0,0 +1,5 @@ +URL | @wailsio/runtime
URL: {
ย ย ย ย prototype: URL;
ย ย ย ย canParse(url: string | URL, base?: string | URL): boolean;
ย ย ย ย createObjectURL(obj: Blob | MediaSource): string;
ย ย ย ย parse(url: string | URL, base?: string | URL): null | URL;
ย ย ย ย revokeObjectURL(url: string): void;
ย ย ย ย new (url: string | URL, base?: string | URL): URL;
}

Type declaration

    • new (url: string | URL, base?: string | URL): URL
    • Parameters

      • url: string | URL
      • Optionalbase: string | URL

      Returns URL

  • prototype: URL
  • canParse:function
    • Parameters

      • url: string | URL
      • Optionalbase: string | URL

      Returns boolean

  • createObjectURL:function
  • parse:function
    • Parameters

      • url: string | URL
      • Optionalbase: string | URL

      Returns null | URL

  • revokeObjectURL:function
    • Parameters

      • url: string

      Returns void

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStream-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStream-1.html new file mode 100644 index 000000000..e31a06f97 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStream-1.html @@ -0,0 +1 @@ +WritableStream | @wailsio/runtime
WritableStream: {
ย ย ย ย prototype: WritableStream;
ย ย ย ย new <W = any>(
ย ย ย ย ย ย ย ย underlyingSink?: UnderlyingSink<W>,
ย ย ย ย ย ย ย ย strategy?: QueuingStrategy<W>,
ย ย ย ย ): WritableStream<W>;
}

Type declaration

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

Variable WritableStreamDefaultController

WritableStreamDefaultController: {
ย ย ย ย prototype: WritableStreamDefaultController;
ย ย ย ย new (): WritableStreamDefaultController;
}
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStreamDefaultWriter-1.html b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStreamDefaultWriter-1.html new file mode 100644 index 000000000..286db85ef --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/_internal_.WritableStreamDefaultWriter-1.html @@ -0,0 +1 @@ +WritableStreamDefaultWriter | @wailsio/runtime

Variable WritableStreamDefaultWriter

WritableStreamDefaultWriter: {
ย ย ย ย prototype: WritableStreamDefaultWriter;
ย ย ย ย new <W = any>(stream: WritableStream<W>): WritableStreamDefaultWriter<W>;
}

Type declaration

diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json new file mode 100644 index 000000000..2c409024a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json @@ -0,0 +1,3140 @@ +{ + "name": "@wailsio/runtime", + "version": "3.0.0-alpha.68", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@wailsio/runtime", + "version": "3.0.0-alpha.68", + "license": "MIT", + "devDependencies": { + "happy-dom": "^17.1.1", + "promises-aplus-tests": "2.1.2", + "rimraf": "^5.0.5", + "typedoc": "^0.27.7", + "typedoc-plugin-markdown": "^4.4.2", + "typedoc-plugin-mdn-links": "^4.0.13", + "typedoc-plugin-missing-exports": "^3.1.0", + "typescript": "^5.7.3", + "vitest": "^3.0.6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", + "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^1.27.2", + "@shikijs/types": "^1.27.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@shikijs/types": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", + "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.7", + "@vitest/utils": "3.0.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", + "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", + "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", + "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.0.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", + "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", + "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", + "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.7", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/happy-dom": { + "version": "17.1.8", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.1.8.tgz", + "integrity": "sha512-Yxbq/FG79z1rhAf/iB6YM8wO2JB/JDQBy99RiLSs+2siEAi5J05x9eW1nnASHZJbpldjJE2KuFLsLZ+AzX/IxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", + "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/promises-aplus-tests": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/promises-aplus-tests/-/promises-aplus-tests-2.1.2.tgz", + "integrity": "sha512-XiDfjQqx+rHLof8CU9xPOMLsjiXXxr3fkjE7WJjUzXttffB8K/nsnNsPTcwS4VvHliSjGVsYVqIjFeTHw53f5w==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "mocha": "^2.5.3", + "sinon": "^1.10.3", + "underscore": "~1.8.3" + }, + "bin": { + "promises-aplus-tests": "lib/cli.js" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typedoc": { + "version": "0.27.9", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz", + "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.6.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.4.2.tgz", + "integrity": "sha512-kJVkU2Wd+AXQpyL6DlYXXRrfNrHrEIUgiABWH8Z+2Lz5Sq6an4dQ/hfvP75bbokjNDUskOdFlEEm/0fSVyC7eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.27.x" + } + }, + "node_modules/typedoc-plugin-mdn-links": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.14.tgz", + "integrity": "sha512-IDILzJr4OzNb5uAWWRMZYny80Q6jUQerAwskdYbNMwaHMRwsB3+y8oqYYE7/PyH+kJVvJnCC4G2wnAQ3CLdgqA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.26.x || 0.27.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-3.1.0.tgz", + "integrity": "sha512-Sogbaj+qDa21NjB3SlIw4JXSwmcl/WOjwiPNaVEcPhpNG/MiRTtpwV81cT7h1cbu9StpONFPbddYWR0KV/fTWA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.26.x || 0.27.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", + "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.5.2", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", + "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", + "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.0.7", + "@vitest/mocker": "3.0.7", + "@vitest/pretty-format": "^3.0.7", + "@vitest/runner": "3.0.7", + "@vitest/snapshot": "3.0.7", + "@vitest/spy": "3.0.7", + "@vitest/utils": "3.0.7", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.7", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.7", + "@vitest/ui": "3.0.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/package.json b/v3/internal/runtime/desktop/@wailsio/runtime/package.json new file mode 100644 index 000000000..578e1609c --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/package.json @@ -0,0 +1,62 @@ +{ + "name": "@wailsio/runtime", + "type": "module", + "version": "3.0.0-alpha.68", + "description": "Wails Runtime", + "types": "types/index.d.ts", + "exports": { + "types": "./types/index.d.ts", + "import": "./dist/index.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git", + "directory": "v3/internal/runtime/desktop/@wailsio/runtime" + }, + "author": "The Wails Team", + "license": "MIT", + "homepage": "https://v3.wails.io", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "files": [ + "./dist", + "./types" + ], + "sideEffects": [ + "./dist/index.js", + "./dist/contextmenu.js", + "./dist/drag.js" + ], + "scripts": { + "check": "npx tsc --noEmit", + "test": "npx vitest run", + "clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo", + "generate:events": "task generate:events", + "generate": "npm run generate:events", + "prebuild": "npm run clean && npm run generate", + "build:code": "npx tsc", + "build:docs": "npx typedoc --gitRevision v3-alpha --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts", + "build:docs:md": "npx typedoc --gitRevision v3-alpha --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts", + "build": "npm run build:code & npm run build:docs & wait", + "prepack": "npm run build" + }, + "devDependencies": { + "happy-dom": "^17.1.1", + "promises-aplus-tests": "2.1.2", + "rimraf": "^5.0.5", + "typedoc": "^0.27.7", + "typedoc-plugin-markdown": "^4.4.2", + "typedoc-plugin-mdn-links": "^4.0.13", + "typedoc-plugin-missing-exports": "^3.1.0", + "typescript": "^5.7.3", + "vitest": "^3.0.6" + }, + "overrides": { + "promises-aplus-tests": { + "mocha": "^11.1.0", + "sinon": "^19.0.2", + "underscore": "^1.13.7" + } + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts new file mode 100644 index 000000000..57a41ac9e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/application.ts @@ -0,0 +1,37 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +const call = newRuntimeCaller(objectNames.Application); + +const HideMethod = 0; +const ShowMethod = 1; +const QuitMethod = 2; + +/** + * Hides a certain method by calling the HideMethod function. + */ +export function Hide(): Promise { + return call(HideMethod); +} + +/** + * Calls the ShowMethod and returns the result. + */ +export function Show(): Promise { + return call(ShowMethod); +} + +/** + * Calls the QuitMethod to terminate the program. + */ +export function Quit(): Promise { + return call(QuitMethod); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts new file mode 100644 index 000000000..465310d3d --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts @@ -0,0 +1,24 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.Browser); + +const BrowserOpenURL = 0; + +/** + * Open a browser window to the given URL. + * + * @param url - The URL to open + */ +export function OpenURL(url: string | URL): Promise { + return call(BrowserOpenURL, {url: url.toString()}); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts new file mode 100644 index 000000000..e8e2e4087 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts @@ -0,0 +1,125 @@ +// Source: https://github.com/inspect-js/is-callable + +// The MIT License (MIT) +// +// Copyright (c) 2015 Jordan Harband +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +var fnToStr = Function.prototype.toString; +var reflectApply: typeof Reflect.apply | false | null = typeof Reflect === 'object' && Reflect !== null && Reflect.apply; +var badArrayLike: any; +var isCallableMarker: any; +if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') { + try { + badArrayLike = Object.defineProperty({}, 'length', { + get: function () { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + // eslint-disable-next-line no-throw-literal + reflectApply(function () { throw 42; }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} + +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value: any): boolean { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; // not a function + } +}; + +var tryFunctionObject = function tryFunctionToStr(value: any): boolean { + try { + if (isES6ClassFn(value)) { return false; } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = '[object Object]'; +var fnClass = '[object Function]'; +var genClass = '[object GeneratorFunction]'; +var ddaClass = '[object HTMLAllCollection]'; // IE 11 +var ddaClass2 = '[object HTML document.all class]'; +var ddaClass3 = '[object HTMLCollection]'; // IE 9-10 +var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag` + +var isIE68 = !(0 in [,]); // eslint-disable-line no-sparse-arrays, comma-spacing + +var isDDA: (value: any) => boolean = function isDocumentDotAll() { return false; }; +if (typeof document === 'object') { + // Firefox 3 canonicalizes DDA to undefined when it's not accessed directly + var all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll(value) { + /* globals document: false */ + // in IE 6-8, typeof document.all is "object" and it's truthy + if ((isIE68 || !value) && (typeof value === 'undefined' || typeof value === 'object')) { + try { + var str = toStr.call(value); + return ( + str === ddaClass + || str === ddaClass2 + || str === ddaClass3 // opera 12.16 + || str === objectClass // IE 6-8 + ) && value('') == null; // eslint-disable-line eqeqeq + } catch (e) { /**/ } + } + return false; + }; + } +} + +function isCallableRefApply(value: T | unknown): value is (...args: any[]) => any { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + try { + (reflectApply as any)(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { return false; } + } + return !isES6ClassFn(value) && tryFunctionObject(value); +} + +function isCallableNoRefApply(value: T | unknown): value is (...args: any[]) => any { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + if (hasToStringTag) { return tryFunctionObject(value); } + if (isES6ClassFn(value)) { return false; } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !(/^\[object HTML/).test(strClass)) { return false; } + return tryFunctionObject(value); +}; + +export default reflectApply ? isCallableRefApply : isCallableNoRefApply; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts new file mode 100644 index 000000000..11d063323 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts @@ -0,0 +1,233 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { CancellablePromise, type CancellablePromiseWithResolvers } from "./cancellable.js"; +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { nanoid } from "./nanoid.js"; + +// Setup +window._wails = window._wails || {}; +window._wails.callResultHandler = resultHandler; +window._wails.callErrorHandler = errorHandler; + +type PromiseResolvers = Omit, "promise" | "oncancelled"> + +const call = newRuntimeCaller(objectNames.Call); +const cancelCall = newRuntimeCaller(objectNames.CancelCall); +const callResponses = new Map(); + +const CallBinding = 0; +const CancelMethod = 0 + +/** + * Holds all required information for a binding call. + * May provide either a method ID or a method name, but not both. + */ +export type CallOptions = { + /** The numeric ID of the bound method to call. */ + methodID: number; + /** The fully qualified name of the bound method to call. */ + methodName?: never; + /** Arguments to be passed into the bound method. */ + args: any[]; +} | { + /** The numeric ID of the bound method to call. */ + methodID?: never; + /** The fully qualified name of the bound method to call. */ + methodName: string; + /** Arguments to be passed into the bound method. */ + args: any[]; +}; + +/** + * Exception class that will be thrown in case the bound method returns an error. + * The value of the {@link RuntimeError#name} property is "RuntimeError". + */ +export class RuntimeError extends Error { + /** + * Constructs a new RuntimeError instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = "RuntimeError"; + } +} + +/** + * Handles the result of a call request. + * + * @param id - The id of the request to handle the result for. + * @param data - The result data of the request. + * @param isJSON - Indicates whether the data is JSON or not. + */ +function resultHandler(id: string, data: string, isJSON: boolean): void { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + + if (!data) { + resolvers.resolve(undefined); + } else if (!isJSON) { + resolvers.resolve(data); + } else { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err: any) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } +} + +/** + * Handles the error from a call request. + * + * @param id - The id of the promise handler. + * @param data - The error data to reject the promise handler with. + * @param isJSON - Indicates whether the data is JSON or not. + */ +function errorHandler(id: string, data: string, isJSON: boolean): void { + const resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + + if (!isJSON) { + resolvers.reject(new Error(data)); + } else { + let error: any; + try { + error = JSON.parse(data); + } catch (err: any) { + resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err })); + return; + } + + let options: ErrorOptions = {}; + if (error.cause) { + options.cause = error.cause; + } + + let exception; + switch (error.kind) { + case "ReferenceError": + exception = new ReferenceError(error.message, options); + break; + case "TypeError": + exception = new TypeError(error.message, options); + break; + case "RuntimeError": + exception = new RuntimeError(error.message, options); + break; + default: + exception = new Error(error.message, options); + break; + } + + resolvers.reject(exception); + } +} + +/** + * Retrieves and removes the response associated with the given ID from the callResponses map. + * + * @param id - The ID of the response to be retrieved and removed. + * @returns The response object associated with the given ID, if any. + */ +function getAndDeleteResponse(id: string): PromiseResolvers | undefined { + const response = callResponses.get(id); + callResponses.delete(id); + return response; +} + +/** + * Generates a unique ID using the nanoid library. + * + * @returns A unique ID that does not exist in the callResponses set. + */ +function generateID(): string { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} + +/** + * Call a bound method according to the given call options. + * + * In case of failure, the returned promise will reject with an exception + * among ReferenceError (unknown method), TypeError (wrong argument count or type), + * {@link RuntimeError} (method returned an error), or other (network or internal errors). + * The exception might have a "cause" field with the value returned + * by the application- or service-level error marshaling functions. + * + * @param options - A method call descriptor. + * @returns The result of the call. + */ +export function Call(options: CallOptions): CancellablePromise { + const id = generateID(); + + const result = CancellablePromise.withResolvers(); + callResponses.set(id, { resolve: result.resolve, reject: result.reject }); + + const request = call(CallBinding, Object.assign({ "call-id": id }, options)); + let running = false; + + request.then(() => { + running = true; + }, (err) => { + callResponses.delete(id); + result.reject(err); + }); + + const cancel = () => { + callResponses.delete(id); + return cancelCall(CancelMethod, {"call-id": id}).catch((err) => { + console.error("Error while requesting binding call cancellation:", err); + }); + }; + + result.oncancelled = () => { + if (running) { + return cancel(); + } else { + return request.then(cancel); + } + }; + + return result.promise; +} + +/** + * Calls a bound method by name with the specified arguments. + * See {@link Call} for details. + * + * @param methodName - The name of the method in the format 'package.struct.method'. + * @param args - The arguments to pass to the method. + * @returns The result of the method call. + */ +export function ByName(methodName: string, ...args: any[]): CancellablePromise { + return Call({ methodName, args }); +} + +/** + * Calls a method by its numeric ID with the specified arguments. + * See {@link Call} for details. + * + * @param methodID - The ID of the method to call. + * @param args - The arguments to pass to the method. + * @return The result of the method call. + */ +export function ByID(methodID: number, ...args: any[]): CancellablePromise { + return Call({ methodID, args }); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js new file mode 100644 index 000000000..b618c1459 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.test.js @@ -0,0 +1,431 @@ +import * as util from "node:util"; +import { describe, it, beforeEach, afterEach, assert, expect, vi } from "vitest"; +import { CancelError, CancellablePromise, CancelledRejectionError } from "./cancellable"; + +// TODO: In order of importance: +// TODO: test cancellation of subpromises the main promise resolves to. +// TODO: test cancellation of promise chains built by calling then() and friends: +// - all promises up the chain should be cancelled; +// - rejection handlers should be always executed with the CancelError of their parent promise in the chain; +// - promises returned from rejection handlers should be cancelled too; +// - if a rejection handler throws or returns a promise that ultimately rejects, +// it should be reported as an unhandled rejection, +// - unless it is a CancelError with the same reason given for cancelling the returned promise. +// TODO: test multiple calls to cancel() (second and later should have no effect). +// TODO: test static factory methods and their cancellation support. + +let expectedUnhandled = new Map(); + +process.on('unhandledRejection', function (error, promise) { + let reason = error; + if (reason instanceof CancelledRejectionError) { + promise = reason.promise; + reason = reason.cause; + } + + let reasons = expectedUnhandled.get(promise); + const callbacks = reasons?.get(reason); + if (callbacks) { + for (const cb of callbacks) { + try { + cb(reason, promise); + } catch (e) { + console.error("Exception in unhandled rejection callback.", e); + } + } + + reasons.delete(reason); + if (reasons.size === 0) { + expectedUnhandled.delete(promise); + } + return; + } + + console.log(util.format("Unhandled rejection.\nReason: %o\nPromise: %o", reason, promise)); + throw error; +}); + +function ignoreUnhandled(reason, promise) { + expectUnhandled(reason, promise, null); +} + +function expectUnhandled(reason, promise, cb) { + let reasons = expectedUnhandled.get(promise); + if (!reasons) { + reasons = new Map(); + expectedUnhandled.set(promise, reasons); + } + let callbacks = reasons.get(reason); + if (!callbacks) { + callbacks = []; + reasons.set(reason, callbacks); + } + if (cb) { + callbacks.push(cb); + } +} + +afterEach(() => { + vi.resetAllMocks(); + vi.restoreAllMocks(); +}); + +const dummyValue = { value: "value" }; +const dummyCause = { dummy: "dummy" }; +const dummyError = new Error("dummy"); +const oncancelled = vi.fn().mockName("oncancelled"); +const sentinel = vi.fn().mockName("sentinel"); +const unhandled = vi.fn().mockName("unhandled"); + +const resolutionPatterns = [ + ["forever", "pending", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => test( + new cls(() => {}, cb) + )], + ["already", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + prw.resolve(value ?? dummyValue); + return test(prw.promise); + }], + ["immediately", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + prw.resolve(value ?? dummyValue); + return tp; + }], + ["eventually", "fulfilled", async (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + await new Promise((resolve) => { + setTimeout(() => { + prw.resolve(value ?? dummyValue); + resolve(); + }, 50); + }); + return tp; + }], + ["already", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + prw.reject(reason ?? dummyError); + return test(prw.promise); + }], + ["immediately", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + prw.reject(reason ?? dummyError); + return tp; + }], + ["eventually", "rejected", async (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => { + const prw = cls.withResolvers(); + prw.oncancelled = cb; + const tp = test(prw.promise); + await new Promise((resolve) => { + setTimeout(() => { + prw.reject(reason ?? dummyError); + resolve(); + }, 50); + }); + return tp; + }], +]; + +describe("CancellablePromise.cancel", ()=> { + it("should suppress its own unhandled cancellation error", async () => { + const p = new CancellablePromise(() => {}); + p.cancel(); + + process.on('unhandledRejection', sentinel); + await new Promise((resolve) => setTimeout(resolve, 100)); + process.off('unhandledRejection', sentinel); + + expect(sentinel).not.toHaveBeenCalled(); + }); + + it.for([ + ["rejections", dummyError], + ["cancellation errors", new CancelError("dummy", { cause: dummyCause })], + ])("should not suppress arbitrary unhandled %s", async ([kind, err]) => { + const p = new CancellablePromise(() => { throw err; }); + p.cancel(); + + await new Promise((resolve) => { + expectUnhandled(err, p, unhandled); + expectUnhandled(err, p, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(err, p); + }); + + describe.for(resolutionPatterns)("when applied to %s %s promises", ([time, state, test]) => { + if (time === "already") { + it("should have no effect", () => test(async (promise) => { + promise.then(sentinel, sentinel); + + let reason; + try { + promise.cancel(); + await promise; + assert(state === "fulfilled", "Promise fulfilled unexpectedly"); + } catch (err) { + reason = err; + assert(state === "rejected", "Promise rejected unexpectedly"); + } + + expect(sentinel).toHaveBeenCalled(); + expect(oncancelled).not.toHaveBeenCalled(); + expect(reason).not.toBeInstanceOf(CancelError); + })); + } else { + if (state === "rejected") { + it("should report late rejections as unhandled", () => test(async (promise) => { + promise.cancel(); + + await new Promise((resolve) => { + expectUnhandled(dummyError, promise, unhandled); + expectUnhandled(dummyError, promise, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise); + })); + } + + it("should reject with a CancelError", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + promise.cancel(); + await promise; + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + })); + + it("should call the oncancelled callback synchronously", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + try { + promise.cancel(); + sentinel(); + await promise; + } catch {} + + expect(oncancelled).toHaveBeenCalledBefore(sentinel); + })); + + it("should propagate the given cause", () => test(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + promise.cancel(dummyCause); + await promise; + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + expect(reason).toHaveProperty('cause', dummyCause); + expect(oncancelled).toHaveBeenCalledWith(reason.cause); + })); + } + }); +}); + +const onabort = vi.fn().mockName("abort"); + +const abortPatterns = [ + ["never", "standalone", (test) => { + const signal = new AbortSignal(); + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["already", "standalone", (test) => { + const signal = AbortSignal.abort(dummyCause); + onabort(); + return test(signal); + }], + ["eventually", "standalone", (test) => { + const signal = AbortSignal.timeout(25); + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["never", "controller-bound", (test) => { + const signal = new AbortController().signal; + signal.addEventListener('abort', onabort, { capture: true }); + return test(signal); + }], + ["already", " controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + ctrl.abort(dummyCause); + return test(ctrl.signal); + }], + ["immediately", "controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + const tp = test(ctrl.signal); + ctrl.abort(dummyCause); + return tp; + }], + ["eventually", "controller-bound", (test) => { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', onabort, { capture: true }); + const tp = test(ctrl.signal); + setTimeout(() => ctrl.abort(dummyCause), 25); + return tp; + }] +]; + +describe("CancellablePromise.cancelOn", ()=> { + it("should return the target promise for chaining", () => { + const p = new CancellablePromise(() => {}); + expect(p.cancelOn(AbortSignal.abort())).toBe(p); + }); + + function tests(abortTime, mode, testSignal, resolveTime, state, testPromise) { + if (abortTime !== "never") { + it(`should call CancellablePromise.cancel ${abortTime === "already" ? "immediately" : "on abort"} with the abort reason as cause`, () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + const cancelSpy = vi.spyOn(promise, 'cancel'); + + promise.catch(() => {}); + promise.cancelOn(signal); + + if (signal.aborted) { + sentinel(); + } else { + await new Promise((resolve) => { + signal.onabort = () => { + sentinel(); + resolve(); + }; + }); + } + + expect(cancelSpy).toHaveBeenCalledAfter(onabort); + expect(cancelSpy).toHaveBeenCalledBefore(sentinel); + expect(cancelSpy).toHaveBeenCalledExactlyOnceWith(signal.reason); + }))); + } + + if ( + resolveTime === "already" + || abortTime === "never" + || ( + ["immediately", "eventually"].includes(abortTime) + && ["already", "immediately"].includes(resolveTime) + ) + ) { + it("should have no effect", () => testSignal((signal) => testPromise(async (promise) => { + promise.then(sentinel, sentinel); + + let reason; + try { + if (resolveTime !== "forever") { + await promise.cancelOn(signal); + assert(state === "fulfilled", "Promise fulfilled unexpectedly"); + } else { + await Promise.race([promise, new Promise((resolve) => setTimeout(resolve, 100))]).then(sentinel); + } + } catch (err) { + reason = err; + assert(state === "rejected", "Promise rejected unexpectedly"); + } + + if (abortTime !== "never" && !signal.aborted) { + // Wait for the AbortSignal to have actually aborted. + await new Promise((resolve) => signal.onabort = resolve); + } + + expect(sentinel).toHaveBeenCalled(); + expect(oncancelled).not.toHaveBeenCalled(); + expect(reason).not.toBeInstanceOf(CancelError); + }))); + } else { + if (state === "rejected") { + it("should report late rejections as unhandled", () => testSignal((signal) => testPromise(async (promise) => { + promise.cancelOn(signal); + + await new Promise((resolve) => { + expectUnhandled(dummyError, promise, unhandled); + expectUnhandled(dummyError, promise, resolve); + }); + + expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise); + }))); + } + + it("should reject with a CancelError", () => testSignal((signal) => testPromise(async (promise)=> { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + await promise.cancelOn(signal); + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + }))); + + it(`should call the oncancelled callback ${abortTime === "already" ? "" : "a"}synchronously`, () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + try { + promise.cancelOn(signal); + sentinel(); + await promise; + } catch {} + + expect(oncancelled).toHaveBeenCalledAfter(onabort); + if (abortTime === "already") { + expect(oncancelled).toHaveBeenCalledBefore(sentinel); + } else { + expect(oncancelled).toHaveBeenCalledAfter(sentinel); + } + }))); + + it("should propagate the abort reason as cause", () => testSignal((signal) => testPromise(async (promise) => { + // Ignore the unhandled rejection from the test promise. + if (state === "rejected") { ignoreUnhandled(dummyError, promise); } + + let reason; + try { + await promise.cancelOn(signal); + } catch (err) { + reason = err; + } + + expect(reason).toBeInstanceOf(CancelError); + expect(reason).toHaveProperty('cause', signal.reason); + expect(oncancelled).toHaveBeenCalledWith(signal.reason); + }))); + } + } + + describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => { + describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => { + tests(abortTime, mode, testSignal, resolveTime, state, testPromise); + }); + }); + + describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => { + describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => { + tests(abortTime, mode, testSignal, resolveTime, state, testPromise); + }); + }); +}); \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts new file mode 100644 index 000000000..5a839e211 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts @@ -0,0 +1,934 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import isCallable from "./callable.js"; + +/** + * Exception class that will be used as rejection reason + * in case a {@link CancellablePromise} is cancelled successfully. + * + * The value of the {@link name} property is the string `"CancelError"`. + * The value of the {@link cause} property is the cause passed to the cancel method, if any. + */ +export class CancelError extends Error { + /** + * Constructs a new `CancelError` instance. + * @param message - The error message. + * @param options - Options to be forwarded to the Error constructor. + */ + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = "CancelError"; + } +} + +/** + * Exception class that will be reported as an unhandled rejection + * in case a {@link CancellablePromise} rejects after being cancelled, + * or when the `oncancelled` callback throws or rejects. + * + * The value of the {@link name} property is the string `"CancelledRejectionError"`. + * The value of the {@link cause} property is the reason the promise rejected with. + * + * Because the original promise was cancelled, + * a wrapper promise will be passed to the unhandled rejection listener instead. + * The {@link promise} property holds a reference to the original promise. + */ +export class CancelledRejectionError extends Error { + /** + * Holds a reference to the promise that was cancelled and then rejected. + */ + promise: CancellablePromise; + + /** + * Constructs a new `CancelledRejectionError` instance. + * @param promise - The promise that caused the error originally. + * @param reason - The rejection reason. + * @param info - An optional informative message specifying the circumstances in which the error was thrown. + * Defaults to the string `"Unhandled rejection in cancelled promise."`. + */ + constructor(promise: CancellablePromise, reason?: any, info?: string) { + super((info ?? "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason }); + this.promise = promise; + this.name = "CancelledRejectionError"; + } +} + +type CancellablePromiseResolver = (value: T | PromiseLike | CancellablePromiseLike) => void; +type CancellablePromiseRejector = (reason?: any) => void; +type CancellablePromiseCanceller = (cause?: any) => void | PromiseLike; +type CancellablePromiseExecutor = (resolve: CancellablePromiseResolver, reject: CancellablePromiseRejector) => void; + +export interface CancellablePromiseLike { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike | CancellablePromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike | CancellablePromiseLike) | undefined | null): CancellablePromiseLike; + cancel(cause?: any): void | PromiseLike; +} + +/** + * Wraps a cancellable promise along with its resolution methods. + * The `oncancelled` field will be null initially but may be set to provide a custom cancellation function. + */ +export interface CancellablePromiseWithResolvers { + promise: CancellablePromise; + resolve: CancellablePromiseResolver; + reject: CancellablePromiseRejector; + oncancelled: CancellablePromiseCanceller | null; +} + +interface CancellablePromiseState { + readonly root: CancellablePromiseState; + resolving: boolean; + settled: boolean; + reason?: CancelError; +} + +// Private field names. +const barrierSym = Symbol("barrier"); +const cancelImplSym = Symbol("cancelImpl"); +const species = Symbol.species ?? Symbol("speciesPolyfill"); + +/** + * A promise with an attached method for cancelling long-running operations (see {@link CancellablePromise#cancel}). + * Cancellation can optionally be bound to an {@link AbortSignal} + * for better composability (see {@link CancellablePromise#cancelOn}). + * + * Cancelling a pending promise will result in an immediate rejection + * with an instance of {@link CancelError} as reason, + * but whoever started the promise will be responsible + * for actually aborting the underlying operation. + * To this purpose, the constructor and all chaining methods + * accept optional cancellation callbacks. + * + * If a `CancellablePromise` still resolves after having been cancelled, + * the result will be discarded. If it rejects, the reason + * will be reported as an unhandled rejection, + * wrapped in a {@link CancelledRejectionError} instance. + * To facilitate the handling of cancellation requests, + * cancelled `CancellablePromise`s will _not_ report unhandled `CancelError`s + * whose `cause` field is the same as the one with which the current promise was cancelled. + * + * All usual promise methods are defined and return a `CancellablePromise` + * whose cancel method will cancel the parent operation as well, propagating the cancellation reason + * upwards through promise chains. + * Conversely, cancelling a promise will not automatically cancel dependent promises downstream: + * ```ts + * let root = new CancellablePromise((resolve, reject) => { ... }); + * let child1 = root.then(() => { ... }); + * let child2 = child1.then(() => { ... }); + * let child3 = root.catch(() => { ... }); + * child1.cancel(); // Cancels child1 and root, but not child2 or child3 + * ``` + * Cancelling a promise that has already settled is safe and has no consequence. + * + * The `cancel` method returns a promise that _always fulfills_ + * after the whole chain has processed the cancel request + * and all attached callbacks up to that moment have run. + * + * All ES2024 promise methods (static and instance) are defined on CancellablePromise, + * but actual availability may vary with OS/webview version. + * + * In line with the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing, + * `CancellablePromise` does not support transparent subclassing. + * Extenders should take care to provide their own method implementations. + * This might be reconsidered in case the proposal is retired. + * + * CancellablePromise is a wrapper around the DOM Promise object + * and is compliant with the [Promises/A+ specification](https://promisesaplus.com/) + * (it passes the [compliance suite](https://github.com/promises-aplus/promises-tests)) + * if so is the underlying implementation. + */ +export class CancellablePromise extends Promise implements PromiseLike, CancellablePromiseLike { + // Private fields. + /** @internal */ + private [barrierSym]!: Partial> | null; + /** @internal */ + private readonly [cancelImplSym]!: (reason: CancelError) => void | PromiseLike; + + /** + * Creates a new `CancellablePromise`. + * + * @param executor - A callback used to initialize the promise. This callback is passed two arguments: + * a `resolve` callback used to resolve the promise with a value + * or the result of another promise (possibly cancellable), + * and a `reject` callback used to reject the promise with a provided reason or error. + * If the value provided to the `resolve` callback is a thenable _and_ cancellable object + * (it has a `then` _and_ a `cancel` method), + * cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore. + * If any one of the two callbacks is called _after_ the promise has been cancelled, + * the provided values will be cancelled and resolved as usual, + * but their results will be discarded. + * However, if the resolution process ultimately ends up in a rejection + * that is not due to cancellation, the rejection reason + * will be wrapped in a {@link CancelledRejectionError} + * and bubbled up as an unhandled rejection. + * @param oncancelled - It is the caller's responsibility to ensure that any operation + * started by the executor is properly halted upon cancellation. + * This optional callback can be used to that purpose. + * It will be called _synchronously_ with a cancellation cause + * when cancellation is requested, _after_ the promise has already rejected + * with a {@link CancelError}, but _before_ + * any {@link then}/{@link catch}/{@link finally} callback runs. + * If the callback returns a thenable, the promise returned from {@link cancel} + * will only fulfill after the former has settled. + * Unhandled exceptions or rejections from the callback will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as unhandled rejections. + * If the `resolve` callback is called before cancellation with a cancellable promise, + * cancellation requests on this promise will be diverted to that promise, + * and the original `oncancelled` callback will be discarded. + */ + constructor(executor: CancellablePromiseExecutor, oncancelled?: CancellablePromiseCanceller) { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + super((res, rej) => { resolve = res; reject = rej; }); + + if ((this.constructor as any)[species] !== Promise) { + throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property."); + } + + let promise: CancellablePromiseWithResolvers = { + promise: this, + resolve, + reject, + get oncancelled() { return oncancelled ?? null; }, + set oncancelled(cb) { oncancelled = cb ?? undefined; } + }; + + const state: CancellablePromiseState = { + get root() { return state; }, + resolving: false, + settled: false + }; + + // Setup cancellation system. + void Object.defineProperties(this, { + [barrierSym]: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + [cancelImplSym]: { + configurable: false, + enumerable: false, + writable: false, + value: cancellerFor(promise, state) + } + }); + + // Run the actual executor. + const rejector = rejectorFor(promise, state); + try { + executor(resolverFor(promise, state), rejector); + } catch (err) { + if (state.resolving) { + console.log("Unhandled exception in CancellablePromise executor.", err); + } else { + rejector(err); + } + } + } + + /** + * Cancels immediately the execution of the operation associated with this promise. + * The promise rejects with a {@link CancelError} instance as reason, + * with the {@link CancelError#cause} property set to the given argument, if any. + * + * Has no effect if called after the promise has already settled; + * repeated calls in particular are safe, but only the first one + * will set the cancellation cause. + * + * The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_ + * cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event. + * Therefore, the following idioms are all equally correct: + * ```ts + * new CancellablePromise((resolve, reject) => { ... }).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).cancel(); + * new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel(); + * ``` + * Whenever some cancelled promise in a chain rejects with a `CancelError` + * with the same cancellation cause as itself, the error will be discarded silently. + * However, the `CancelError` _will still be delivered_ to all attached rejection handlers + * added by {@link then} and related methods: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * cancellable.then(() => { ... }).catch(console.log); + * cancellable.cancel(); // A CancelError is printed to the console. + * ``` + * If the `CancelError` is not handled downstream by the time it reaches + * a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event, + * just like normal rejections would: + * ```ts + * let cancellable = new CancellablePromise((resolve, reject) => { ... }); + * let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch... + * cancellable.cancel(); // Unhandled rejection event on chained! + * ``` + * Therefore, it is important to either cancel whole promise chains from their tail, + * as shown in the correct idioms above, or take care of handling errors everywhere. + * + * @returns A cancellable promise that _fulfills_ after the cancel callback (if any) + * and all handlers attached up to the call to cancel have run. + * If the cancel callback returns a thenable, the promise returned by `cancel` + * will also wait for that thenable to settle. + * This enables callers to wait for the cancelled operation to terminate + * without being forced to handle potential errors at the call site. + * ```ts + * cancellable.cancel().then(() => { + * // Cleanup finished, it's safe to do something else. + * }, (err) => { + * // Unreachable: the promise returned from cancel will never reject. + * }); + * ``` + * Note that the returned promise will _not_ handle implicitly any rejection + * that might have occurred already in the cancelled chain. + * It will just track whether registered handlers have been executed or not. + * Therefore, unhandled rejections will never be silently handled by calling cancel. + */ + cancel(cause?: any): CancellablePromise { + return new CancellablePromise((resolve) => { + // INVARIANT: the result of this[cancelImplSym] and the barrier do not ever reject. + // Unfortunately macOS High Sierra does not support Promise.allSettled. + Promise.all([ + this[cancelImplSym](new CancelError("Promise cancelled.", { cause })), + currentBarrier(this) + ]).then(() => resolve(), () => resolve()); + }); + } + + /** + * Binds promise cancellation to the abort event of the given {@link AbortSignal}. + * If the signal has already aborted, the promise will be cancelled immediately. + * When either condition is verified, the cancellation cause will be set + * to the signal's abort reason (see {@link AbortSignal#reason}). + * + * Has no effect if called (or if the signal aborts) _after_ the promise has already settled. + * Only the first signal to abort will set the cancellation cause. + * + * For more details about the cancellation process, + * see {@link cancel} and the `CancellablePromise` constructor. + * + * This method enables `await`ing cancellable promises without having + * to store them for future cancellation, e.g.: + * ```ts + * await longRunningOperation().cancelOn(signal); + * ``` + * instead of: + * ```ts + * let promiseToBeCancelled = longRunningOperation(); + * await promiseToBeCancelled; + * ``` + * + * @returns This promise, for method chaining. + */ + cancelOn(signal: AbortSignal): CancellablePromise { + if (signal.aborted) { + void this.cancel(signal.reason) + } else { + signal.addEventListener('abort', () => void this.cancel(signal.reason), {capture: true}); + } + + return this; + } + + /** + * Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A `CancellablePromise` for the completion of whichever callback is executed. + * The returned promise is hooked up to propagate cancellation requests up the chain, but not down: + * + * - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError` + * and the returned promise _will resolve regularly_ with its result; + * - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_ + * the `onrejected` handler will still be invoked with the parent's `CancelError`, + * but its result will be discarded + * and the returned promise will reject with a `CancelError` as well. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If either callback returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike | CancellablePromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike | CancellablePromiseLike) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.then called on an invalid object."); + } + + // NOTE: TypeScript's built-in type for then is broken, + // as it allows specifying an arbitrary TResult1 != T even when onfulfilled is not a function. + // We cannot fix it if we want to CancellablePromise to implement PromiseLike. + + if (!isCallable(onfulfilled)) { onfulfilled = identity as any; } + if (!isCallable(onrejected)) { onrejected = thrower; } + + if (onfulfilled === identity && onrejected == thrower) { + // Shortcut for trivial arguments. + return new CancellablePromise((resolve) => resolve(this as any)); + } + + const barrier: Partial> = {}; + this[barrierSym] = barrier; + + return new CancellablePromise((resolve, reject) => { + void super.then( + (value) => { + if (this[barrierSym] === barrier) { this[barrierSym] = null; } + barrier.resolve?.(); + + try { + resolve(onfulfilled!(value)); + } catch (err) { + reject(err); + } + }, + (reason?) => { + if (this[barrierSym] === barrier) { this[barrierSym] = null; } + barrier.resolve?.(); + + try { + resolve(onrejected!(reason)); + } catch (err) { + reject(err); + } + } + ); + }, async (cause?) => { + //cancelled = true; + try { + return oncancelled?.(cause); + } finally { + await this.cancel(cause); + } + }); + } + + /** + * Attaches a callback for only the rejection of the Promise. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * When the parent promise rejects or is cancelled, the `onrejected` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * It is equivalent to + * ```ts + * cancellablePromise.then(undefined, onrejected, oncancelled); + * ``` + * and the same caveats apply. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onrejected` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + catch(onrejected?: ((reason: any) => (PromiseLike | TResult)) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + return this.then(undefined, onrejected, oncancelled); + } + + /** + * Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The + * resolved value cannot be accessed or modified from the callback. + * The returned promise will settle in the same state as the original one + * after the provided callback has completed execution, + * unless the callback throws or returns a rejecting promise, + * in which case the returned promise will reject as well. + * + * The optional `oncancelled` argument will be invoked when the returned promise is cancelled, + * with the same semantics as the `oncancelled` argument of the constructor. + * Once the parent promise settles, the `onfinally` callback will run, + * _even after the returned promise has been cancelled:_ + * in that case, should it reject or throw, the reason will be wrapped + * in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection. + * + * This method is implemented in terms of {@link then} and the same caveats apply. + * It is polyfilled, hence available in every OS/webview version. + * + * @returns A Promise for the completion of the callback. + * Cancellation requests on the returned promise + * will propagate up the chain to the parent promise, + * but not in the other direction. + * + * The promise returned from {@link cancel} will fulfill only after all attached handlers + * up the entire promise chain have been run. + * + * If `onfinally` returns a cancellable promise, + * cancellation requests will be diverted to it, + * and the specified `oncancelled` callback will be discarded. + * See {@link then} for more details. + */ + finally(onfinally?: (() => void) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise { + if (!(this instanceof CancellablePromise)) { + throw new TypeError("CancellablePromise.prototype.finally called on an invalid object."); + } + + if (!isCallable(onfinally)) { + return this.then(onfinally, onfinally, oncancelled); + } + + return this.then( + (value) => CancellablePromise.resolve(onfinally()).then(() => value), + (reason?) => CancellablePromise.resolve(onfinally()).then(() => { throw reason; }), + oncancelled, + ); + } + + /** + * We use the `[Symbol.species]` static property, if available, + * to disable the built-in automatic subclassing features from {@link Promise}. + * It is critical for performance reasons that extenders do not override this. + * Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing + * is either accepted or retired, this implementation will have to be revised accordingly. + * + * @ignore + * @internal + */ + static get [species]() { + return Promise; + } + + /** + * Creates a CancellablePromise that is resolved with an array of results + * when all of the provided Promises resolve, or rejected when any Promise is rejected. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static all(values: Iterable>): CancellablePromise[]>; + static all(values: T): CancellablePromise<{ -readonly [P in keyof T]: Awaited; }>; + static all | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.all(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a CancellablePromise that is resolved with an array of results + * when all of the provided Promises resolve or reject. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static allSettled(values: Iterable>): CancellablePromise>[]>; + static allSettled(values: T): CancellablePromise<{ -readonly [P in keyof T]: PromiseSettledResult>; }>; + static allSettled | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.allSettled(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, + * or rejected with an AggregateError containing an array of rejection reasons + * if all of the given promises are rejected. + * It resolves all elements of the passed iterable to promises as it runs this algorithm. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static any(values: Iterable>): CancellablePromise>; + static any(values: T): CancellablePromise>; + static any | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = collected.length === 0 + ? CancellablePromise.resolve(collected) + : new CancellablePromise((resolve, reject) => { + void Promise.any(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved or rejected. + * + * Every one of the provided objects that is a thenable _and_ cancellable object + * will be cancelled when the returned promise is cancelled, with the same cause. + * + * @group Static Methods + */ + static race(values: Iterable>): CancellablePromise>; + static race(values: T): CancellablePromise>; + static race | ArrayLike>(values: T): CancellablePromise { + let collected = Array.from(values); + const promise = new CancellablePromise((resolve, reject) => { + void Promise.race(collected).then(resolve, reject); + }, (cause?): Promise => cancelAll(promise, collected, cause)); + return promise; + } + + /** + * Creates a new cancelled CancellablePromise for the provided cause. + * + * @group Static Methods + */ + static cancel(cause?: any): CancellablePromise { + const p = new CancellablePromise(() => {}); + p.cancel(cause); + return p; + } + + /** + * Creates a new CancellablePromise that cancels + * after the specified timeout, with the provided cause. + * + * If the {@link AbortSignal.timeout} factory method is available, + * it is used to base the timeout on _active_ time rather than _elapsed_ time. + * Otherwise, `timeout` falls back to {@link setTimeout}. + * + * @group Static Methods + */ + static timeout(milliseconds: number, cause?: any): CancellablePromise { + const promise = new CancellablePromise(() => {}); + if (AbortSignal && typeof AbortSignal === 'function' && AbortSignal.timeout && typeof AbortSignal.timeout === 'function') { + AbortSignal.timeout(milliseconds).addEventListener('abort', () => void promise.cancel(cause)); + } else { + setTimeout(() => void promise.cancel(cause), milliseconds); + } + return promise; + } + + /** + * Creates a new CancellablePromise that resolves after the specified timeout. + * The returned promise can be cancelled without consequences. + * + * @group Static Methods + */ + static sleep(milliseconds: number): CancellablePromise; + /** + * Creates a new CancellablePromise that resolves after + * the specified timeout, with the provided value. + * The returned promise can be cancelled without consequences. + * + * @group Static Methods + */ + static sleep(milliseconds: number, value: T): CancellablePromise; + static sleep(milliseconds: number, value?: T): CancellablePromise { + return new CancellablePromise((resolve) => { + setTimeout(() => resolve(value!), milliseconds); + }); + } + + /** + * Creates a new rejected CancellablePromise for the provided reason. + * + * @group Static Methods + */ + static reject(reason?: any): CancellablePromise { + return new CancellablePromise((_, reject) => reject(reason)); + } + + /** + * Creates a new resolved CancellablePromise. + * + * @group Static Methods + */ + static resolve(): CancellablePromise; + /** + * Creates a new resolved CancellablePromise for the provided value. + * + * @group Static Methods + */ + static resolve(value: T): CancellablePromise>; + /** + * Creates a new resolved CancellablePromise for the provided value. + * + * @group Static Methods + */ + static resolve(value: T | PromiseLike): CancellablePromise>; + static resolve(value?: T | PromiseLike): CancellablePromise> { + if (value instanceof CancellablePromise) { + // Optimise for cancellable promises. + return value; + } + return new CancellablePromise((resolve) => resolve(value)); + } + + /** + * Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions + * and a getter/setter for the cancellation callback. + * + * This method is polyfilled, hence available in every OS/webview version. + * + * @group Static Methods + */ + static withResolvers(): CancellablePromiseWithResolvers { + let result: CancellablePromiseWithResolvers = { oncancelled: null } as any; + result.promise = new CancellablePromise((resolve, reject) => { + result.resolve = resolve; + result.reject = reject; + }, (cause?: any) => { result.oncancelled?.(cause); }); + return result; + } +} + +/** + * Returns a callback that implements the cancellation algorithm for the given cancellable promise. + * The promise returned from the resulting function does not reject. + */ +function cancellerFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState) { + let cancellationPromise: void | PromiseLike = undefined; + + return (reason: CancelError): void | PromiseLike => { + if (!state.settled) { + state.settled = true; + state.reason = reason; + promise.reject(reason); + + // Attach an error handler that ignores this specific rejection reason and nothing else. + // In theory, a sane underlying implementation at this point + // should always reject with our cancellation reason, + // hence the handler will never throw. + void Promise.prototype.then.call(promise.promise, undefined, (err) => { + if (err !== reason) { + throw err; + } + }); + } + + // If reason is not set, the promise resolved regularly, hence we must not call oncancelled. + // If oncancelled is unset, no need to go any further. + if (!state.reason || !promise.oncancelled) { return; } + + cancellationPromise = new Promise((resolve) => { + try { + resolve(promise.oncancelled!(state.reason!.cause)); + } catch (err) { + Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback.")); + } + }).catch((reason?) => { + Promise.reject(new CancelledRejectionError(promise.promise, reason, "Unhandled rejection in oncancelled callback.")); + }); + + // Unset oncancelled to prevent repeated calls. + promise.oncancelled = null; + + return cancellationPromise; + } +} + +/** + * Returns a callback that implements the resolution algorithm for the given cancellable promise. + */ +function resolverFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState): CancellablePromiseResolver { + return (value) => { + if (state.resolving) { return; } + state.resolving = true; + + if (value === promise.promise) { + if (state.settled) { return; } + state.settled = true; + promise.reject(new TypeError("A promise cannot be resolved with itself.")); + return; + } + + if (value != null && (typeof value === 'object' || typeof value === 'function')) { + let then: any; + try { + then = (value as any).then; + } catch (err) { + state.settled = true; + promise.reject(err); + return; + } + + if (isCallable(then)) { + try { + let cancel = (value as any).cancel; + if (isCallable(cancel)) { + const oncancelled = (cause?: any) => { + Reflect.apply(cancel, value, [cause]); + }; + if (state.reason) { + // If already cancelled, propagate cancellation. + // The promise returned from the canceller algorithm does not reject + // so it can be discarded safely. + void cancellerFor({ ...promise, oncancelled }, state)(state.reason); + } else { + promise.oncancelled = oncancelled; + } + } + } catch {} + + const newState: CancellablePromiseState = { + root: state.root, + resolving: false, + get settled() { return this.root.settled }, + set settled(value) { this.root.settled = value; }, + get reason() { return this.root.reason } + }; + + const rejector = rejectorFor(promise, newState); + try { + Reflect.apply(then, value, [resolverFor(promise, newState), rejector]); + } catch (err) { + rejector(err); + } + return; // IMPORTANT! + } + } + + if (state.settled) { return; } + state.settled = true; + promise.resolve(value); + }; +} + +/** + * Returns a callback that implements the rejection algorithm for the given cancellable promise. + */ +function rejectorFor(promise: CancellablePromiseWithResolvers, state: CancellablePromiseState): CancellablePromiseRejector { + return (reason?) => { + if (state.resolving) { return; } + state.resolving = true; + + if (state.settled) { + try { + if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) { + // Swallow late rejections that are CancelErrors whose cancellation cause is the same as ours. + return; + } + } catch {} + + void Promise.reject(new CancelledRejectionError(promise.promise, reason)); + } else { + state.settled = true; + promise.reject(reason); + } + } +} + +/** + * Cancels all values in an array that look like cancellable thenables. + * Returns a promise that fulfills once all cancellation procedures for the given values have settled. + */ +function cancelAll(parent: CancellablePromise, values: any[], cause?: any): Promise { + const results = []; + + for (const value of values) { + let cancel: CancellablePromiseCanceller; + try { + if (!isCallable(value.then)) { continue; } + cancel = value.cancel; + if (!isCallable(cancel)) { continue; } + } catch { continue; } + + let result: void | PromiseLike; + try { + result = Reflect.apply(cancel, value, [cause]); + } catch (err) { + Promise.reject(new CancelledRejectionError(parent, err, "Unhandled exception in cancel method.")); + continue; + } + + if (!result) { continue; } + results.push( + (result instanceof Promise ? result : Promise.resolve(result)).catch((reason?) => { + Promise.reject(new CancelledRejectionError(parent, reason, "Unhandled rejection in cancel method.")); + }) + ); + } + + return Promise.all(results) as any; +} + +/** + * Returns its argument. + */ +function identity(x: T): T { + return x; +} + +/** + * Throws its argument. + */ +function thrower(reason?: any): never { + throw reason; +} + +/** + * Attempts various strategies to convert an error to a string. + */ +function errorMessage(err: any): string { + try { + if (err instanceof Error || typeof err !== 'object' || err.toString !== Object.prototype.toString) { + return "" + err; + } + } catch {} + + try { + return JSON.stringify(err); + } catch {} + + try { + return Object.prototype.toString.call(err); + } catch {} + + return ""; +} + +/** + * Gets the current barrier promise for the given cancellable promise. If necessary, initialises the barrier. + */ +function currentBarrier(promise: CancellablePromise): Promise { + let pwr: Partial> = promise[barrierSym] ?? {}; + if (!('promise' in pwr)) { + Object.assign(pwr, promiseWithResolvers()); + } + if (promise[barrierSym] == null) { + pwr.resolve!(); + promise[barrierSym] = pwr; + } + return pwr.promise!; +} + +// Polyfill Promise.withResolvers. +let promiseWithResolvers = Promise.withResolvers; +if (promiseWithResolvers && typeof promiseWithResolvers === 'function') { + promiseWithResolvers = promiseWithResolvers.bind(Promise); +} else { + promiseWithResolvers = function (): PromiseWithResolvers { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + return { promise, resolve, reject }; + } +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts new file mode 100644 index 000000000..a6f2f1985 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/clipboard.ts @@ -0,0 +1,35 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.Clipboard); + +const ClipboardSetText = 0; +const ClipboardText = 1; + +/** + * Sets the text to the Clipboard. + * + * @param text - The text to be set to the Clipboard. + * @return A Promise that resolves when the operation is successful. + */ +export function SetText(text: string): Promise { + return call(ClipboardSetText, {text}); +} + +/** + * Get the Clipboard text + * + * @returns A promise that resolves with the text from the Clipboard. + */ +export function Text(): Promise { + return call(ClipboardText); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts new file mode 100644 index 000000000..5f15bf237 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/contextmenu.ts @@ -0,0 +1,94 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { IsDebug } from "./system.js"; +import { eventTarget } from "./utils"; + +// setup +window.addEventListener('contextmenu', contextMenuHandler); + +const call = newRuntimeCaller(objectNames.ContextMenu); + +const ContextMenuOpen = 0; + +function openContextMenu(id: string, x: number, y: number, data: any): void { + void call(ContextMenuOpen, {id, x, y, data}); +} + +function contextMenuHandler(event: MouseEvent) { + const target = eventTarget(event); + + // Check for custom context menu + const customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim(); + + if (customContextMenu) { + event.preventDefault(); + const data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data"); + openContextMenu(customContextMenu, event.clientX, event.clientY, data); + } else { + processDefaultContextMenu(event, target); + } +} + + +/* +--default-contextmenu: auto; (default) will show the default context menu if contentEditable is true OR text has been selected OR element is input or textarea +--default-contextmenu: show; will always show the default context menu +--default-contextmenu: hide; will always hide the default context menu + +This rule is inherited like normal CSS rules, so nesting works as expected +*/ +function processDefaultContextMenu(event: MouseEvent, target: HTMLElement) { + // Debug builds always show the menu + if (IsDebug()) { + return; + } + + // Process default context menu + switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) { + case 'show': + return; + case 'hide': + event.preventDefault(); + return; + } + + // Check if contentEditable is true + if (target.isContentEditable) { + return; + } + + // Check if text has been selected + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; + if (hasSelection) { + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + const rects = range.getClientRects(); + for (let j = 0; j < rects.length; j++) { + const rect = rects[j]; + if (document.elementFromPoint(rect.left, rect.top) === target) { + return; + } + } + } + } + + // Check if tag is input or textarea. + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (hasSelection || (!target.readOnly && !target.disabled)) { + return; + } + } + + // hide default context menu + event.preventDefault(); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts new file mode 100644 index 000000000..72965eaa6 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts @@ -0,0 +1,106 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Any is a dummy creation function for simple or unknown types. + */ +export function Any(source: any): T { + return source; +} + +/** + * ByteSlice is a creation function that replaces + * null strings with empty strings. + */ +export function ByteSlice(source: any): string { + return ((source == null) ? "" : source); +} + +/** + * Array takes a creation function for an arbitrary type + * and returns an in-place creation function for an array + * whose elements are of that type. + */ +export function Array(element: (source: any) => T): (source: any) => T[] { + if (element === Any) { + return (source) => (source === null ? [] : source); + } + + return (source) => { + if (source === null) { + return []; + } + for (let i = 0; i < source.length; i++) { + source[i] = element(source[i]); + } + return source; + }; +} + +/** + * Map takes creation functions for two arbitrary types + * and returns an in-place creation function for an object + * whose keys and values are of those types. + */ +export function Map(key: (source: any) => string, value: (source: any) => V): (source: any) => Record { + if (value === Any) { + return (source) => (source === null ? {} : source); + } + + return (source) => { + if (source === null) { + return {}; + } + for (const key in source) { + source[key] = value(source[key]); + } + return source; + }; +} + +/** + * Nullable takes a creation function for an arbitrary type + * and returns a creation function for a nullable value of that type. + */ +export function Nullable(element: (source: any) => T): (source: any) => (T | null) { + if (element === Any) { + return Any; + } + + return (source) => (source === null ? null : element(source)); +} + +/** + * Struct takes an object mapping field names to creation functions + * and returns an in-place creation function for a struct. + */ +export function Struct(createField: Record any>): + = any>(source: any) => U +{ + let allAny = true; + for (const name in createField) { + if (createField[name] !== Any) { + allAny = false; + break; + } + } + if (allAny) { + return Any; + } + + return (source) => { + for (const name in createField) { + if (name in source) { + source[name] = createField[name](source[name]); + } + } + return source; + }; +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts new file mode 100644 index 000000000..bb0625b2e --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts @@ -0,0 +1,255 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; +import { nanoid } from './nanoid.js'; + +// setup +window._wails = window._wails || {}; +window._wails.dialogErrorCallback = dialogErrorCallback; +window._wails.dialogResultCallback = dialogResultCallback; + +type PromiseResolvers = Omit, "promise">; + +const call = newRuntimeCaller(objectNames.Dialog); +const dialogResponses = new Map(); + +// Define constants from the `methods` object in Title Case +const DialogInfo = 0; +const DialogWarning = 1; +const DialogError = 2; +const DialogQuestion = 3; +const DialogOpenFile = 4; +const DialogSaveFile = 5; + +export interface OpenFileDialogOptions { + /** Indicates if directories can be chosen. */ + CanChooseDirectories?: boolean; + /** Indicates if files can be chosen. */ + CanChooseFiles?: boolean; + /** Indicates if directories can be created. */ + CanCreateDirectories?: boolean; + /** Indicates if hidden files should be shown. */ + ShowHiddenFiles?: boolean; + /** Indicates if aliases should be resolved. */ + ResolvesAliases?: boolean; + /** Indicates if multiple selection is allowed. */ + AllowsMultipleSelection?: boolean; + /** Indicates if the extension should be hidden. */ + HideExtension?: boolean; + /** Indicates if hidden extensions can be selected. */ + CanSelectHiddenExtension?: boolean; + /** Indicates if file packages should be treated as directories. */ + TreatsFilePackagesAsDirectories?: boolean; + /** Indicates if other file types are allowed. */ + AllowsOtherFiletypes?: boolean; + /** Array of file filters. */ + Filters?: FileFilter[]; + /** Title of the dialog. */ + Title?: string; + /** Message to show in the dialog. */ + Message?: string; + /** Text to display on the button. */ + ButtonText?: string; + /** Directory to open in the dialog. */ + Directory?: string; + /** Indicates if the dialog should appear detached from the main window. */ + Detached?: boolean; +} + +export interface SaveFileDialogOptions { + /** Default filename to use in the dialog. */ + Filename?: string; + /** Indicates if directories can be chosen. */ + CanChooseDirectories?: boolean; + /** Indicates if files can be chosen. */ + CanChooseFiles?: boolean; + /** Indicates if directories can be created. */ + CanCreateDirectories?: boolean; + /** Indicates if hidden files should be shown. */ + ShowHiddenFiles?: boolean; + /** Indicates if aliases should be resolved. */ + ResolvesAliases?: boolean; + /** Indicates if the extension should be hidden. */ + HideExtension?: boolean; + /** Indicates if hidden extensions can be selected. */ + CanSelectHiddenExtension?: boolean; + /** Indicates if file packages should be treated as directories. */ + TreatsFilePackagesAsDirectories?: boolean; + /** Indicates if other file types are allowed. */ + AllowsOtherFiletypes?: boolean; + /** Array of file filters. */ + Filters?: FileFilter[]; + /** Title of the dialog. */ + Title?: string; + /** Message to show in the dialog. */ + Message?: string; + /** Text to display on the button. */ + ButtonText?: string; + /** Directory to open in the dialog. */ + Directory?: string; + /** Indicates if the dialog should appear detached from the main window. */ + Detached?: boolean; +} + +export interface MessageDialogOptions { + /** The title of the dialog window. */ + Title?: string; + /** The main message to show in the dialog. */ + Message?: string; + /** Array of button options to show in the dialog. */ + Buttons?: Button[]; + /** True if the dialog should appear detached from the main window (if applicable). */ + Detached?: boolean; +} + +export interface Button { + /** Text that appears within the button. */ + Label?: string; + /** True if the button should cancel an operation when clicked. */ + IsCancel?: boolean; + /** True if the button should be the default action when the user presses enter. */ + IsDefault?: boolean; +} + +export interface FileFilter { + /** Display name for the filter, it could be "Text Files", "Images" etc. */ + DisplayName?: string; + /** Pattern to match for the filter, e.g. "*.txt;*.md" for text markdown files. */ + Pattern?: string; +} + +/** + * Handles the result of a dialog request. + * + * @param id - The id of the request to handle the result for. + * @param data - The result data of the request. + * @param isJSON - Indicates whether the data is JSON or not. + */ +function dialogResultCallback(id: string, data: string, isJSON: boolean): void { + let resolvers = getAndDeleteResponse(id); + if (!resolvers) { + return; + } + + if (isJSON) { + try { + resolvers.resolve(JSON.parse(data)); + } catch (err: any) { + resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err })); + } + } else { + resolvers.resolve(data); + } +} + +/** + * Handles the error from a dialog request. + * + * @param id - The id of the promise handler. + * @param message - An error message. + */ +function dialogErrorCallback(id: string, message: string): void { + getAndDeleteResponse(id)?.reject(new window.Error(message)); +} + +/** + * Retrieves and removes the response associated with the given ID from the dialogResponses map. + * + * @param id - The ID of the response to be retrieved and removed. + * @returns The response object associated with the given ID, if any. + */ +function getAndDeleteResponse(id: string): PromiseResolvers | undefined { + const response = dialogResponses.get(id); + dialogResponses.delete(id); + return response; +} + +/** + * Generates a unique ID using the nanoid library. + * + * @returns A unique ID that does not exist in the dialogResponses set. + */ +function generateID(): string { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; +} + +/** + * Presents a dialog of specified type with the given options. + * + * @param type - Dialog type. + * @param options - Options for the dialog. + * @returns A promise that resolves with result of dialog. + */ +function dialog(type: number, options: MessageDialogOptions | OpenFileDialogOptions | SaveFileDialogOptions = {}): Promise { + const id = generateID(); + return new Promise((resolve, reject) => { + dialogResponses.set(id, { resolve, reject }); + call(type, Object.assign({ "dialog-id": id }, options)).catch((err: any) => { + dialogResponses.delete(id); + reject(err); + }); + }); +} + +/** + * Presents an info dialog. + * + * @param options - Dialog options + * @returns A promise that resolves with the label of the chosen button. + */ +export function Info(options: MessageDialogOptions): Promise { return dialog(DialogInfo, options); } + +/** + * Presents a warning dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Warning(options: MessageDialogOptions): Promise { return dialog(DialogWarning, options); } + +/** + * Presents an error dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Error(options: MessageDialogOptions): Promise { return dialog(DialogError, options); } + +/** + * Presents a question dialog. + * + * @param options - Dialog options. + * @returns A promise that resolves with the label of the chosen button. + */ +export function Question(options: MessageDialogOptions): Promise { return dialog(DialogQuestion, options); } + +/** + * Presents a file selection dialog to pick one or more files to open. + * + * @param options - Dialog options. + * @returns Selected file or list of files, or a blank string/empty list if no file has been selected. + */ +export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection: true }): Promise; +export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection?: false | undefined }): Promise; +export function OpenFile(options: OpenFileDialogOptions): Promise; +export function OpenFile(options: OpenFileDialogOptions): Promise { return dialog(DialogOpenFile, options) ?? []; } + +/** + * Presents a file selection dialog to pick a file to save. + * + * @param options - Dialog options. + * @returns Selected file, or a blank string if no file has been selected. + */ +export function SaveFile(options: SaveFileDialogOptions): Promise { return dialog(DialogSaveFile, options); } diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts new file mode 100644 index 000000000..919c03c2c --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts @@ -0,0 +1,237 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { invoke, IsWindows } from "./system.js"; +import { GetFlag } from "./flags.js"; +import { canTrackButtons, eventTarget } from "./utils.js"; + +// Setup +let canDrag = false; +let dragging = false; + +let resizable = false; +let canResize = false; +let resizing = false; +let resizeEdge: string = ""; +let defaultCursor = "auto"; + +let buttons = 0; +const buttonsTracked = canTrackButtons(); + +window._wails = window._wails || {}; +window._wails.setResizable = (value: boolean): void => { + resizable = value; + if (!resizable) { + // Stop resizing if in progress. + canResize = resizing = false; + setResize(); + } +}; + +window.addEventListener('mousedown', update, { capture: true }); +window.addEventListener('mousemove', update, { capture: true }); +window.addEventListener('mouseup', update, { capture: true }); +for (const ev of ['click', 'contextmenu', 'dblclick']) { + window.addEventListener(ev, suppressEvent, { capture: true }); +} + +function suppressEvent(event: Event) { + // Suppress click events while resizing or dragging. + if (dragging || resizing) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } +} + +// Use constants to avoid comparing strings multiple times. +const MouseDown = 0; +const MouseUp = 1; +const MouseMove = 2; + +function update(event: MouseEvent) { + // Windows suppresses mouse events at the end of dragging or resizing, + // so we need to be smart and synthesize button events. + + let eventType: number, eventButtons = event.buttons; + switch (event.type) { + case 'mousedown': + eventType = MouseDown; + if (!buttonsTracked) { eventButtons = buttons | (1 << event.button); } + break; + case 'mouseup': + eventType = MouseUp; + if (!buttonsTracked) { eventButtons = buttons & ~(1 << event.button); } + break; + default: + eventType = MouseMove; + if (!buttonsTracked) { eventButtons = buttons; } + break; + } + + let released = buttons & ~eventButtons; + let pressed = eventButtons & ~buttons; + + buttons = eventButtons; + + // Synthesize a release-press sequence if we detect a press of an already pressed button. + if (eventType === MouseDown && !(pressed & event.button)) { + released |= (1 << event.button); + pressed |= (1 << event.button); + } + + // Suppress all button events during dragging and resizing, + // unless this is a mouseup event that is ending a drag action. + if ( + eventType !== MouseMove // Fast path for mousemove + && resizing + || ( + dragging + && ( + eventType === MouseDown + || event.button !== 0 + ) + ) + ) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + + // Handle releases + if (released & 1) { primaryUp(event); } + // Handle presses + if (pressed & 1) { primaryDown(event); } + + // Handle mousemove + if (eventType === MouseMove) { onMouseMove(event); }; +} + +function primaryDown(event: MouseEvent): void { + // Reset readiness state. + canDrag = false; + canResize = false; + + // Ignore repeated clicks on macOS and Linux. + if (!IsWindows()) { + if (event.type === 'mousedown' && event.button === 0 && event.detail !== 1) { + return; + } + } + + if (resizeEdge) { + // Ready to resize if the primary button was pressed for the first time. + canResize = true; + // Do not start drag operations when on resize edges. + return; + } + + // Retrieve target element + const target = eventTarget(event); + + // Ready to drag if the primary button was pressed for the first time on a draggable element. + // Ignore clicks on the scrollbar. + const style = window.getComputedStyle(target); + canDrag = ( + style.getPropertyValue("--wails-draggable").trim() === "drag" + && ( + event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth + && event.offsetY - parseFloat(style.paddingTop) < target.clientHeight + ) + ); +} + +function primaryUp(event: MouseEvent) { + // Stop dragging and resizing. + canDrag = false; + dragging = false; + canResize = false; + resizing = false; +} + +const cursorForEdge = Object.freeze({ + "se-resize": "nwse-resize", + "sw-resize": "nesw-resize", + "nw-resize": "nwse-resize", + "ne-resize": "nesw-resize", + "w-resize": "ew-resize", + "n-resize": "ns-resize", + "s-resize": "ns-resize", + "e-resize": "ew-resize", +}) + +function setResize(edge?: keyof typeof cursorForEdge): void { + if (edge) { + if (!resizeEdge) { defaultCursor = document.body.style.cursor; } + document.body.style.cursor = cursorForEdge[edge]; + } else if (!edge && resizeEdge) { + document.body.style.cursor = defaultCursor; + } + + resizeEdge = edge || ""; +} + +function onMouseMove(event: MouseEvent): void { + if (canResize && resizeEdge) { + // Start resizing. + resizing = true; + invoke("wails:resize:" + resizeEdge); + } else if (canDrag) { + // Start dragging. + dragging = true; + invoke("wails:drag"); + } + + if (dragging || resizing) { + // Either drag or resize is ongoing, + // reset readiness and stop processing. + canDrag = canResize = false; + return; + } + + if (!resizable || !IsWindows()) { + if (resizeEdge) { setResize(); } + return; + } + + const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5; + const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5; + + // Extra pixels for the corner areas. + const cornerExtra = GetFlag("resizeCornerExtra") || 10; + + const rightBorder = (window.outerWidth - event.clientX) < resizeHandleWidth; + const leftBorder = event.clientX < resizeHandleWidth; + const topBorder = event.clientY < resizeHandleHeight; + const bottomBorder = (window.outerHeight - event.clientY) < resizeHandleHeight; + + // Adjust for corner areas. + const rightCorner = (window.outerWidth - event.clientX) < (resizeHandleWidth + cornerExtra); + const leftCorner = event.clientX < (resizeHandleWidth + cornerExtra); + const topCorner = event.clientY < (resizeHandleHeight + cornerExtra); + const bottomCorner = (window.outerHeight - event.clientY) < (resizeHandleHeight + cornerExtra); + + if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) { + // Optimisation: out of all corner areas implies out of borders. + setResize(); + } + // Detect corners. + else if (rightCorner && bottomCorner) setResize("se-resize"); + else if (leftCorner && bottomCorner) setResize("sw-resize"); + else if (leftCorner && topCorner) setResize("nw-resize"); + else if (topCorner && rightCorner) setResize("ne-resize"); + // Detect borders. + else if (leftBorder) setResize("w-resize"); + else if (topBorder) setResize("n-resize"); + else if (bottomBorder) setResize("s-resize"); + else if (rightBorder) setResize("e-resize"); + // Out of border area. + else setResize(); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts new file mode 100644 index 000000000..9360224da --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts @@ -0,0 +1,233 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export const Types = Object.freeze({ + Windows: Object.freeze({ + APMPowerSettingChange: "windows:APMPowerSettingChange", + APMPowerStatusChange: "windows:APMPowerStatusChange", + APMResumeAutomatic: "windows:APMResumeAutomatic", + APMResumeSuspend: "windows:APMResumeSuspend", + APMSuspend: "windows:APMSuspend", + ApplicationStarted: "windows:ApplicationStarted", + SystemThemeChanged: "windows:SystemThemeChanged", + WebViewNavigationCompleted: "windows:WebViewNavigationCompleted", + WindowActive: "windows:WindowActive", + WindowBackgroundErase: "windows:WindowBackgroundErase", + WindowClickActive: "windows:WindowClickActive", + WindowClosing: "windows:WindowClosing", + WindowDidMove: "windows:WindowDidMove", + WindowDidResize: "windows:WindowDidResize", + WindowDPIChanged: "windows:WindowDPIChanged", + WindowDragDrop: "windows:WindowDragDrop", + WindowDragEnter: "windows:WindowDragEnter", + WindowDragLeave: "windows:WindowDragLeave", + WindowDragOver: "windows:WindowDragOver", + WindowEndMove: "windows:WindowEndMove", + WindowEndResize: "windows:WindowEndResize", + WindowFullscreen: "windows:WindowFullscreen", + WindowHide: "windows:WindowHide", + WindowInactive: "windows:WindowInactive", + WindowKeyDown: "windows:WindowKeyDown", + WindowKeyUp: "windows:WindowKeyUp", + WindowKillFocus: "windows:WindowKillFocus", + WindowNonClientHit: "windows:WindowNonClientHit", + WindowNonClientMouseDown: "windows:WindowNonClientMouseDown", + WindowNonClientMouseLeave: "windows:WindowNonClientMouseLeave", + WindowNonClientMouseMove: "windows:WindowNonClientMouseMove", + WindowNonClientMouseUp: "windows:WindowNonClientMouseUp", + WindowPaint: "windows:WindowPaint", + WindowRestore: "windows:WindowRestore", + WindowSetFocus: "windows:WindowSetFocus", + WindowShow: "windows:WindowShow", + WindowStartMove: "windows:WindowStartMove", + WindowStartResize: "windows:WindowStartResize", + WindowUnFullscreen: "windows:WindowUnFullscreen", + WindowZOrderChanged: "windows:WindowZOrderChanged", + WindowMinimise: "windows:WindowMinimise", + WindowUnMinimise: "windows:WindowUnMinimise", + WindowMaximise: "windows:WindowMaximise", + WindowUnMaximise: "windows:WindowUnMaximise", + }), + Mac: Object.freeze({ + ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive", + ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties", + ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance", + ApplicationDidChangeIcon: "mac:ApplicationDidChangeIcon", + ApplicationDidChangeOcclusionState: "mac:ApplicationDidChangeOcclusionState", + ApplicationDidChangeScreenParameters: "mac:ApplicationDidChangeScreenParameters", + ApplicationDidChangeStatusBarFrame: "mac:ApplicationDidChangeStatusBarFrame", + ApplicationDidChangeStatusBarOrientation: "mac:ApplicationDidChangeStatusBarOrientation", + ApplicationDidChangeTheme: "mac:ApplicationDidChangeTheme", + ApplicationDidFinishLaunching: "mac:ApplicationDidFinishLaunching", + ApplicationDidHide: "mac:ApplicationDidHide", + ApplicationDidResignActive: "mac:ApplicationDidResignActive", + ApplicationDidUnhide: "mac:ApplicationDidUnhide", + ApplicationDidUpdate: "mac:ApplicationDidUpdate", + ApplicationShouldHandleReopen: "mac:ApplicationShouldHandleReopen", + ApplicationWillBecomeActive: "mac:ApplicationWillBecomeActive", + ApplicationWillFinishLaunching: "mac:ApplicationWillFinishLaunching", + ApplicationWillHide: "mac:ApplicationWillHide", + ApplicationWillResignActive: "mac:ApplicationWillResignActive", + ApplicationWillTerminate: "mac:ApplicationWillTerminate", + ApplicationWillUnhide: "mac:ApplicationWillUnhide", + ApplicationWillUpdate: "mac:ApplicationWillUpdate", + MenuDidAddItem: "mac:MenuDidAddItem", + MenuDidBeginTracking: "mac:MenuDidBeginTracking", + MenuDidClose: "mac:MenuDidClose", + MenuDidDisplayItem: "mac:MenuDidDisplayItem", + MenuDidEndTracking: "mac:MenuDidEndTracking", + MenuDidHighlightItem: "mac:MenuDidHighlightItem", + MenuDidOpen: "mac:MenuDidOpen", + MenuDidPopUp: "mac:MenuDidPopUp", + MenuDidRemoveItem: "mac:MenuDidRemoveItem", + MenuDidSendAction: "mac:MenuDidSendAction", + MenuDidSendActionToItem: "mac:MenuDidSendActionToItem", + MenuDidUpdate: "mac:MenuDidUpdate", + MenuWillAddItem: "mac:MenuWillAddItem", + MenuWillBeginTracking: "mac:MenuWillBeginTracking", + MenuWillDisplayItem: "mac:MenuWillDisplayItem", + MenuWillEndTracking: "mac:MenuWillEndTracking", + MenuWillHighlightItem: "mac:MenuWillHighlightItem", + MenuWillOpen: "mac:MenuWillOpen", + MenuWillPopUp: "mac:MenuWillPopUp", + MenuWillRemoveItem: "mac:MenuWillRemoveItem", + MenuWillSendAction: "mac:MenuWillSendAction", + MenuWillSendActionToItem: "mac:MenuWillSendActionToItem", + MenuWillUpdate: "mac:MenuWillUpdate", + WebViewDidCommitNavigation: "mac:WebViewDidCommitNavigation", + WebViewDidFinishNavigation: "mac:WebViewDidFinishNavigation", + WebViewDidReceiveServerRedirectForProvisionalNavigation: "mac:WebViewDidReceiveServerRedirectForProvisionalNavigation", + WebViewDidStartProvisionalNavigation: "mac:WebViewDidStartProvisionalNavigation", + WindowDidBecomeKey: "mac:WindowDidBecomeKey", + WindowDidBecomeMain: "mac:WindowDidBecomeMain", + WindowDidBeginSheet: "mac:WindowDidBeginSheet", + WindowDidChangeAlpha: "mac:WindowDidChangeAlpha", + WindowDidChangeBackingLocation: "mac:WindowDidChangeBackingLocation", + WindowDidChangeBackingProperties: "mac:WindowDidChangeBackingProperties", + WindowDidChangeCollectionBehavior: "mac:WindowDidChangeCollectionBehavior", + WindowDidChangeEffectiveAppearance: "mac:WindowDidChangeEffectiveAppearance", + WindowDidChangeOcclusionState: "mac:WindowDidChangeOcclusionState", + WindowDidChangeOrderingMode: "mac:WindowDidChangeOrderingMode", + WindowDidChangeScreen: "mac:WindowDidChangeScreen", + WindowDidChangeScreenParameters: "mac:WindowDidChangeScreenParameters", + WindowDidChangeScreenProfile: "mac:WindowDidChangeScreenProfile", + WindowDidChangeScreenSpace: "mac:WindowDidChangeScreenSpace", + WindowDidChangeScreenSpaceProperties: "mac:WindowDidChangeScreenSpaceProperties", + WindowDidChangeSharingType: "mac:WindowDidChangeSharingType", + WindowDidChangeSpace: "mac:WindowDidChangeSpace", + WindowDidChangeSpaceOrderingMode: "mac:WindowDidChangeSpaceOrderingMode", + WindowDidChangeTitle: "mac:WindowDidChangeTitle", + WindowDidChangeToolbar: "mac:WindowDidChangeToolbar", + WindowDidDeminiaturize: "mac:WindowDidDeminiaturize", + WindowDidEndSheet: "mac:WindowDidEndSheet", + WindowDidEnterFullScreen: "mac:WindowDidEnterFullScreen", + WindowDidEnterVersionBrowser: "mac:WindowDidEnterVersionBrowser", + WindowDidExitFullScreen: "mac:WindowDidExitFullScreen", + WindowDidExitVersionBrowser: "mac:WindowDidExitVersionBrowser", + WindowDidExpose: "mac:WindowDidExpose", + WindowDidFocus: "mac:WindowDidFocus", + WindowDidMiniaturize: "mac:WindowDidMiniaturize", + WindowDidMove: "mac:WindowDidMove", + WindowDidOrderOffScreen: "mac:WindowDidOrderOffScreen", + WindowDidOrderOnScreen: "mac:WindowDidOrderOnScreen", + WindowDidResignKey: "mac:WindowDidResignKey", + WindowDidResignMain: "mac:WindowDidResignMain", + WindowDidResize: "mac:WindowDidResize", + WindowDidUpdate: "mac:WindowDidUpdate", + WindowDidUpdateAlpha: "mac:WindowDidUpdateAlpha", + WindowDidUpdateCollectionBehavior: "mac:WindowDidUpdateCollectionBehavior", + WindowDidUpdateCollectionProperties: "mac:WindowDidUpdateCollectionProperties", + WindowDidUpdateShadow: "mac:WindowDidUpdateShadow", + WindowDidUpdateTitle: "mac:WindowDidUpdateTitle", + WindowDidUpdateToolbar: "mac:WindowDidUpdateToolbar", + WindowDidZoom: "mac:WindowDidZoom", + WindowFileDraggingEntered: "mac:WindowFileDraggingEntered", + WindowFileDraggingExited: "mac:WindowFileDraggingExited", + WindowFileDraggingPerformed: "mac:WindowFileDraggingPerformed", + WindowHide: "mac:WindowHide", + WindowMaximise: "mac:WindowMaximise", + WindowUnMaximise: "mac:WindowUnMaximise", + WindowMinimise: "mac:WindowMinimise", + WindowUnMinimise: "mac:WindowUnMinimise", + WindowShouldClose: "mac:WindowShouldClose", + WindowShow: "mac:WindowShow", + WindowWillBecomeKey: "mac:WindowWillBecomeKey", + WindowWillBecomeMain: "mac:WindowWillBecomeMain", + WindowWillBeginSheet: "mac:WindowWillBeginSheet", + WindowWillChangeOrderingMode: "mac:WindowWillChangeOrderingMode", + WindowWillClose: "mac:WindowWillClose", + WindowWillDeminiaturize: "mac:WindowWillDeminiaturize", + WindowWillEnterFullScreen: "mac:WindowWillEnterFullScreen", + WindowWillEnterVersionBrowser: "mac:WindowWillEnterVersionBrowser", + WindowWillExitFullScreen: "mac:WindowWillExitFullScreen", + WindowWillExitVersionBrowser: "mac:WindowWillExitVersionBrowser", + WindowWillFocus: "mac:WindowWillFocus", + WindowWillMiniaturize: "mac:WindowWillMiniaturize", + WindowWillMove: "mac:WindowWillMove", + WindowWillOrderOffScreen: "mac:WindowWillOrderOffScreen", + WindowWillOrderOnScreen: "mac:WindowWillOrderOnScreen", + WindowWillResignMain: "mac:WindowWillResignMain", + WindowWillResize: "mac:WindowWillResize", + WindowWillUnfocus: "mac:WindowWillUnfocus", + WindowWillUpdate: "mac:WindowWillUpdate", + WindowWillUpdateAlpha: "mac:WindowWillUpdateAlpha", + WindowWillUpdateCollectionBehavior: "mac:WindowWillUpdateCollectionBehavior", + WindowWillUpdateCollectionProperties: "mac:WindowWillUpdateCollectionProperties", + WindowWillUpdateShadow: "mac:WindowWillUpdateShadow", + WindowWillUpdateTitle: "mac:WindowWillUpdateTitle", + WindowWillUpdateToolbar: "mac:WindowWillUpdateToolbar", + WindowWillUpdateVisibility: "mac:WindowWillUpdateVisibility", + WindowWillUseStandardFrame: "mac:WindowWillUseStandardFrame", + WindowZoomIn: "mac:WindowZoomIn", + WindowZoomOut: "mac:WindowZoomOut", + WindowZoomReset: "mac:WindowZoomReset", + }), + Linux: Object.freeze({ + ApplicationStartup: "linux:ApplicationStartup", + SystemThemeChanged: "linux:SystemThemeChanged", + WindowDeleteEvent: "linux:WindowDeleteEvent", + WindowDidMove: "linux:WindowDidMove", + WindowDidResize: "linux:WindowDidResize", + WindowFocusIn: "linux:WindowFocusIn", + WindowFocusOut: "linux:WindowFocusOut", + WindowLoadChanged: "linux:WindowLoadChanged", + }), + Common: Object.freeze({ + ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile", + ApplicationStarted: "common:ApplicationStarted", + ApplicationLaunchedWithUrl: "common:ApplicationLaunchedWithUrl", + ThemeChanged: "common:ThemeChanged", + WindowClosing: "common:WindowClosing", + WindowDidMove: "common:WindowDidMove", + WindowDidResize: "common:WindowDidResize", + WindowDPIChanged: "common:WindowDPIChanged", + WindowFilesDropped: "common:WindowFilesDropped", + WindowFocus: "common:WindowFocus", + WindowFullscreen: "common:WindowFullscreen", + WindowHide: "common:WindowHide", + WindowLostFocus: "common:WindowLostFocus", + WindowMaximise: "common:WindowMaximise", + WindowMinimise: "common:WindowMinimise", + WindowToggleFrameless: "common:WindowToggleFrameless", + WindowRestore: "common:WindowRestore", + WindowRuntimeReady: "common:WindowRuntimeReady", + WindowShow: "common:WindowShow", + WindowUnFullscreen: "common:WindowUnFullscreen", + WindowUnMaximise: "common:WindowUnMaximise", + WindowUnMinimise: "common:WindowUnMinimise", + WindowZoom: "common:WindowZoom", + WindowZoomIn: "common:WindowZoomIn", + WindowZoomOut: "common:WindowZoomOut", + WindowZoomReset: "common:WindowZoomReset", + }), +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js new file mode 100644 index 000000000..e8157a17a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.test.js @@ -0,0 +1,155 @@ +import { On, Off, OffAll, OnMultiple, WailsEvent, Once } from './events'; +import { eventListeners } from "./listener"; +import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest'; + +const dispatchWailsEvent = window._wails.dispatchWailsEvent; + +afterEach(() => { + OffAll(); + vi.resetAllMocks(); +}); + +describe("OnMultiple", () => { + const testEvent = { name: 'a', data: ["hello", "events"] }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should stop after the specified number of times", () => { + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(5); + }); + + it("should return a cancel fn", () => { + const cancel = OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + cancel(); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toBeCalledTimes(2); + }); +}); + +describe("On", () => { + let testEvent = { name: 'a', data: ["hello", "events"], sender: "window" }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + On('a', cb); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should never stop", () => { + On('a', cb); + expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1); + dispatchWailsEvent(testEvent); + expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1); + }); + + it("should return a cancel fn", () => { + const cancel = On('a', cb) + dispatchWailsEvent(testEvent); + cancel(); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(1); + }); +}); + +describe("Once", () => { + const testEvent = { name: 'a', data: ["hello", "events"] }; + const cb = vi.fn((ev) => { + expect(ev).toBeInstanceOf(WailsEvent); + expect(ev).toMatchObject(testEvent); + }); + + it("should dispatch a properly initialised WailsEvent", () => { + Once('a', cb); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalled(); + }); + + it("should stop after one time", () => { + Once('a', cb) + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it("should return a cancel fn", () => { + const cancel = Once('a', cb) + cancel(); + dispatchWailsEvent(testEvent); + expect(cb).not.toHaveBeenCalled(); + }); +}) + +describe("Off", () => { + const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn(); + + beforeEach(() => { + On('a', cba); + On('a', cba); + On('a', cba); + On('b', cbb); + On('c', cbc); + On('c', cbc); + }); + + it("should cancel all event listeners for a single type", () => { + Off('a'); + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).toHaveBeenCalledTimes(1); + expect(cbc).toHaveBeenCalledTimes(2); + }); + + it("should cancel all event listeners for multiple types", () => { + Off('a', 'c') + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).toHaveBeenCalledTimes(1); + expect(cbc).not.toHaveBeenCalled(); + }); +}); + +describe("OffAll", () => { + it("should cancel all event listeners", () => { + const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn(); + On('a', cba); + On('a', cba); + On('a', cba); + On('b', cbb); + On('c', cbc); + On('c', cbc); + OffAll(); + dispatchWailsEvent({ name: 'a' }); + dispatchWailsEvent({ name: 'b' }); + dispatchWailsEvent({ name: 'c' }); + expect(cba).not.toHaveBeenCalled(); + expect(cbb).not.toHaveBeenCalled(); + expect(cbc).not.toHaveBeenCalled(); + }); +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts new file mode 100644 index 000000000..7ad7642b6 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts @@ -0,0 +1,150 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +import { eventListeners, Listener, listenerOff } from "./listener.js"; + +// Setup +window._wails = window._wails || {}; +window._wails.dispatchWailsEvent = dispatchWailsEvent; + +const call = newRuntimeCaller(objectNames.Events); +const EmitMethod = 0; + +export { Types } from "./event_types.js"; + +/** + * The type of handlers for a given event. + */ +export type Callback = (ev: WailsEvent) => void; + +/** + * Represents a system event or a custom event emitted through wails-provided facilities. + */ +export class WailsEvent { + /** + * The name of the event. + */ + name: string; + + /** + * Optional data associated with the emitted event. + */ + data: any; + + /** + * Name of the originating window. Omitted for application events. + * Will be overridden if set manually. + */ + sender?: string; + + constructor(name: string, data: any = null) { + this.name = name; + this.data = data; + } +} + +function dispatchWailsEvent(event: any) { + let listeners = eventListeners.get(event.name); + if (!listeners) { + return; + } + + let wailsEvent = new WailsEvent(event.name, event.data); + if ('sender' in event) { + wailsEvent.sender = event.sender; + } + + listeners = listeners.filter(listener => !listener.dispatch(wailsEvent)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } +} + +/** + * Register a callback function to be called multiple times for a specific event. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @param maxCallbacks - The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function OnMultiple(eventName: string, callback: Callback, maxCallbacks: number) { + let listeners = eventListeners.get(eventName) || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + listeners.push(thisListener); + eventListeners.set(eventName, listeners); + return () => listenerOff(thisListener); +} + +/** + * Registers a callback function to be executed when the specified event occurs. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function On(eventName: string, callback: Callback): () => void { + return OnMultiple(eventName, callback, -1); +} + +/** + * Registers a callback function to be executed only once for the specified event. + * + * @param eventName - The name of the event to register the callback for. + * @param callback - The callback function to be called when the event is triggered. + * @returns A function that, when called, will unregister the callback from the event. + */ +export function Once(eventName: string, callback: Callback): () => void { + return OnMultiple(eventName, callback, 1); +} + +/** + * Removes event listeners for the specified event names. + * + * @param eventNames - The name of the events to remove listeners for. + */ +export function Off(...eventNames: [string, ...string[]]): void { + eventNames.forEach(eventName => eventListeners.delete(eventName)); +} + +/** + * Removes all event listeners. + */ +export function OffAll(): void { + eventListeners.clear(); +} + +/** + * Emits an event using the name and data. + * + * @returns A promise that will be fulfilled once the event has been emitted. + * @param name - the name of the event to emit. + * @param data - the data to be sent with the event. + */ +export function Emit(name: string, data?: any): Promise { + let eventName: string; + let eventData: any; + + if (typeof name === 'object' && name !== null && 'name' in name && 'data' in name) { + // If name is an object with a name property, use it directly + eventName = name['name']; + eventData = name['data']; + } else { + // Otherwise use the standard parameters + eventName = name as string; + eventData = data; + } + + return call(EmitMethod, { name: eventName, data: eventData }); +} + diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts new file mode 100644 index 000000000..9e4ad2427 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts @@ -0,0 +1,23 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Retrieves the value associated with the specified key from the flag map. + * + * @param key - The key to retrieve the value for. + * @return The value associated with the specified key. + */ +export function GetFlag(key: string): any { + try { + return window._wails.flags[key]; + } catch (e) { + throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e }); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts new file mode 100644 index 000000000..231896ce1 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts @@ -0,0 +1,17 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +declare global { + interface Window { + _wails: Record; + } +} + +export {}; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts new file mode 100644 index 000000000..09d0dc243 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts @@ -0,0 +1,57 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// Setup +window._wails = window._wails || {}; + +import "./contextmenu.js"; +import "./drag.js"; + +// Re-export public API +import * as Application from "./application.js"; +import * as Browser from "./browser.js"; +import * as Call from "./calls.js"; +import * as Clipboard from "./clipboard.js"; +import * as Create from "./create.js"; +import * as Dialogs from "./dialogs.js"; +import * as Events from "./events.js"; +import * as Flags from "./flags.js"; +import * as Screens from "./screens.js"; +import * as System from "./system.js"; +import Window from "./window.js"; +import * as WML from "./wml.js"; + +export { + Application, + Browser, + Call, + Clipboard, + Dialogs, + Events, + Flags, + Screens, + System, + Window, + WML +}; + +/** + * An internal utility consumed by the binding generator. + * + * @ignore + * @internal + */ +export { Create }; + +export * from "./cancellable.js"; + +// Notify backend +window._wails.invoke = System.invoke; +System.invoke("wails:runtime:ready"); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts new file mode 100644 index 000000000..0d74debca --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts @@ -0,0 +1,52 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// The following utilities have been factored out of ./events.ts +// for testing purposes. + +export const eventListeners = new Map(); + +export class Listener { + eventName: string; + callback: (data: any) => void; + maxCallbacks: number; + + constructor(eventName: string, callback: (data: any) => void, maxCallbacks: number) { + this.eventName = eventName; + this.callback = callback; + this.maxCallbacks = maxCallbacks || -1; + } + + dispatch(data: any): boolean { + try { + this.callback(data); + } catch (err) { + console.error(err); + } + + if (this.maxCallbacks === -1) return false; + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + } +} + +export function listenerOff(listener: Listener): void { + let listeners = eventListeners.get(listener.eventName); + if (!listeners) { + return; + } + + listeners = listeners.filter(l => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(listener.eventName); + } else { + eventListeners.set(listener.eventName, listeners); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts new file mode 100644 index 000000000..bfe83048f --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/nanoid.ts @@ -0,0 +1,42 @@ +// Source: https://github.com/ai/nanoid + +// The MIT License (MIT) +// +// Copyright 2017 Andrey Sitnik +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This alphabet uses `A-Za-z0-9_-` symbols. +// The order of characters is optimized for better gzip and brotli compression. +// References to the same file (works both for gzip and brotli): +// `'use`, `andom`, and `rict'` +// References to the brotli default dictionary: +// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` +const urlAlphabet = + 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' + +export function nanoid(size: number = 21): string { + let id = '' + // A compact alternative for `for (var i = 0; i < step; i++)`. + let i = size | 0 + while (i--) { + // `| 0` is more compact and faster than `Math.floor()`. + id += urlAlphabet[(Math.random() * 64) | 0] + } + return id +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js b/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js new file mode 100644 index 000000000..baf51e3c0 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/promises_aplus.test.js @@ -0,0 +1,44 @@ +import * as util from "util"; +import * as V from "vitest"; +import { CancellablePromise } from "./cancellable"; + +// The Promises/A+ suite handles some errors late. +process.on('rejectionHandled', function () {}); + +// The Promises/A+ suite leaves some errors unhandled. +process.on('unhandledRejection', function (reason, promise) { + if (promise instanceof CancellablePromise && reason != null && typeof reason === 'object') { + for (const key of ['dummy', 'other', 'sentinel']) { + if (reason[key] === key) { + return; + } + } + } + throw new Error(`Unhandled rejection at: ${util.inspect(promise)}; reason: ${util.inspect(reason)}`, { cause: reason }); +}); + +// Emulate a minimal version of the mocha BDD API using vitest primitives. +global.context = global.describe = V.describe; +global.specify = global.it = function it(desc, fn) { + let viTestFn = fn; + if (fn?.length) { + viTestFn = () => new Promise((done) => fn(done)); + } + V.it(desc, viTestFn); +} +global.before = function(desc, fn) { V.beforeAll(typeof desc === 'function' ? desc : fn) }; +global.after = function(desc, fn) { V.afterAll(typeof desc === 'function' ? desc : fn) }; +global.beforeEach = function(desc, fn) { V.beforeEach(typeof desc === 'function' ? desc : fn) }; +global.afterEach = function(desc, fn) { V.afterEach(typeof desc === 'function' ? desc : fn) }; + +require('promises-aplus-tests').mocha({ + resolved(value) { + return CancellablePromise.resolve(value); + }, + rejected(reason) { + return CancellablePromise.reject(reason); + }, + deferred() { + return CancellablePromise.withResolvers(); + } +}); diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts new file mode 100644 index 000000000..412427ef5 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts @@ -0,0 +1,67 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { nanoid } from './nanoid.js'; + +const runtimeURL = window.location.origin + "/wails/runtime"; + +// Object Names +export const objectNames = Object.freeze({ + Call: 0, + Clipboard: 1, + Application: 2, + Events: 3, + ContextMenu: 4, + Dialog: 5, + Window: 6, + Screens: 7, + System: 8, + Browser: 9, + CancelCall: 10, +}); +export let clientId = nanoid(); + +/** + * Creates a new runtime caller with specified ID. + * + * @param object - The object to invoke the method on. + * @param windowName - The name of the window. + * @return The new runtime caller function. + */ +export function newRuntimeCaller(object: number, windowName: string = '') { + return function (method: number, args: any = null) { + return runtimeCallWithID(object, method, windowName, args); + }; +} + +async function runtimeCallWithID(objectID: number, method: number, windowName: string, args: any): Promise { + let url = new URL(runtimeURL); + url.searchParams.append("object", objectID.toString()); + url.searchParams.append("method", method.toString()); + if (args) { url.searchParams.append("args", JSON.stringify(args)); } + + let headers: Record = { + ["x-wails-client-id"]: clientId + } + if (windowName) { + headers["x-wails-window-name"] = windowName; + } + + let response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(await response.text()); + } + + if ((response.headers.get("Content-Type")?.indexOf("application/json") ?? -1) !== -1) { + return response.json(); + } else { + return response.text(); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts new file mode 100644 index 000000000..c0ecfd7be --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts @@ -0,0 +1,88 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Size { + /** The width of a rectangular area. */ + Width: number; + /** The height of a rectangular area. */ + Height: number; +} + +export interface Rect { + /** The X coordinate of the origin. */ + X: number; + /** The Y coordinate of the origin. */ + Y: number; + /** The width of the rectangle. */ + Width: number; + /** The height of the rectangle. */ + Height: number; +} + +export interface Screen { + /** Unique identifier for the screen. */ + ID: string; + /** Human-readable name of the screen. */ + Name: string; + /** The scale factor of the screen (DPI/96). 1 = standard DPI, 2 = HiDPI (Retina), etc. */ + ScaleFactor: number; + /** The X coordinate of the screen. */ + X: number; + /** The Y coordinate of the screen. */ + Y: number; + /** Contains the width and height of the screen. */ + Size: Size; + /** Contains the bounds of the screen in terms of X, Y, Width, and Height. */ + Bounds: Rect; + /** Contains the physical bounds of the screen in terms of X, Y, Width, and Height (before scaling). */ + PhysicalBounds: Rect; + /** Contains the area of the screen that is actually usable (excluding taskbar and other system UI). */ + WorkArea: Rect; + /** Contains the physical WorkArea of the screen (before scaling). */ + PhysicalWorkArea: Rect; + /** True if this is the primary monitor selected by the user in the operating system. */ + IsPrimary: boolean; + /** The rotation of the screen. */ + Rotation: number; +} + +import { newRuntimeCaller, objectNames } from "./runtime.js"; +const call = newRuntimeCaller(objectNames.Screens); + +const getAll = 0; +const getPrimary = 1; +const getCurrent = 2; + +/** + * Gets all screens. + * + * @returns A promise that resolves to an array of Screen objects. + */ +export function GetAll(): Promise { + return call(getAll); +} + +/** + * Gets the primary screen. + * + * @returns A promise that resolves to the primary screen. + */ +export function GetPrimary(): Promise { + return call(getPrimary); +} + +/** + * Gets the current active screen. + * + * @returns A promise that resolves with the current active screen. + */ +export function GetCurrent(): Promise { + return call(getCurrent); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts new file mode 100644 index 000000000..aa95ecd24 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts @@ -0,0 +1,156 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { newRuntimeCaller, objectNames } from "./runtime.js"; + +const call = newRuntimeCaller(objectNames.System); + +const SystemIsDarkMode = 0; +const SystemEnvironment = 1; + +const _invoke = (function () { + try { + if ((window as any).chrome?.webview?.postMessage) { + return (window as any).chrome.webview.postMessage.bind((window as any).chrome.webview); + } else if ((window as any).webkit?.messageHandlers?.['external']?.postMessage) { + return (window as any).webkit.messageHandlers['external'].postMessage.bind((window as any).webkit.messageHandlers['external']); + } + } catch(e) {} + + console.warn('\n%cโš ๏ธ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n', + 'background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;', + 'background: transparent;', + 'color: #ffffff; font-style: italic; font-weight: bold;'); + return null; +})(); + +export function invoke(msg: any): void { + _invoke?.(msg); +} + +/** + * Retrieves the system dark mode status. + * + * @returns A promise that resolves to a boolean value indicating if the system is in dark mode. + */ +export function IsDarkMode(): Promise { + return call(SystemIsDarkMode); +} + +/** + * Fetches the capabilities of the application from the server. + * + * @returns A promise that resolves to an object containing the capabilities. + */ +export async function Capabilities(): Promise> { + let response = await fetch("/wails/capabilities"); + if (response.ok) { + return response.json(); + } else { + throw new Error("could not fetch capabilities: " + response.statusText); + } +} + +export interface OSInfo { + /** The branding of the OS. */ + Branding: string; + /** The ID of the OS. */ + ID: string; + /** The name of the OS. */ + Name: string; + /** The version of the OS. */ + Version: string; +} + +export interface EnvironmentInfo { + /** The architecture of the system. */ + Arch: string; + /** True if the application is running in debug mode, otherwise false. */ + Debug: boolean; + /** The operating system in use. */ + OS: string; + /** Details of the operating system. */ + OSInfo: OSInfo; + /** Additional platform information. */ + PlatformInfo: Record; +} + +/** + * Retrieves environment details. + * + * @returns A promise that resolves to an object containing OS and system architecture. + */ +export function Environment(): Promise { + return call(SystemEnvironment); +} + +/** + * Checks if the current operating system is Windows. + * + * @return True if the operating system is Windows, otherwise false. + */ +export function IsWindows(): boolean { + return window._wails.environment.OS === "windows"; +} + +/** + * Checks if the current operating system is Linux. + * + * @returns Returns true if the current operating system is Linux, false otherwise. + */ +export function IsLinux(): boolean { + return window._wails.environment.OS === "linux"; +} + +/** + * Checks if the current environment is a macOS operating system. + * + * @returns True if the environment is macOS, false otherwise. + */ +export function IsMac(): boolean { + return window._wails.environment.OS === "darwin"; +} + +/** + * Checks if the current environment architecture is AMD64. + * + * @returns True if the current environment architecture is AMD64, false otherwise. + */ +export function IsAMD64(): boolean { + return window._wails.environment.Arch === "amd64"; +} + +/** + * Checks if the current architecture is ARM. + * + * @returns True if the current architecture is ARM, false otherwise. + */ +export function IsARM(): boolean { + return window._wails.environment.Arch === "arm"; +} + +/** + * Checks if the current environment is ARM64 architecture. + * + * @returns Returns true if the environment is ARM64 architecture, otherwise returns false. + */ +export function IsARM64(): boolean { + return window._wails.environment.Arch === "arm64"; +} + +/** + * Reports whether the app is being run in debug mode. + * + * @returns True if the app is being run in debug mode. + */ +export function IsDebug(): boolean { + return Boolean(window._wails.environment.Debug); +} + diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts new file mode 100644 index 000000000..35b09463b --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/utils.ts @@ -0,0 +1,105 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/** + * Logs a message to the console with custom formatting. + * + * @param message - The message to be logged. + */ +export function debugLog(message: any) { + // eslint-disable-next-line + console.log( + '%c wails3 %c ' + message + ' ', + 'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem', + 'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem' + ); +} + +/** + * Checks whether the webview supports the {@link MouseEvent#buttons} property. + * Looking at you macOS High Sierra! + */ +export function canTrackButtons(): boolean { + return (new MouseEvent('mousedown')).buttons === 0; +} + +/** + * Checks whether the browser supports removing listeners by triggering an AbortSignal + * (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal). + */ +export function canAbortListeners() { + if (!EventTarget || !AbortSignal || !AbortController) + return false; + + let result = true; + + const target = new EventTarget(); + const controller = new AbortController(); + target.addEventListener('test', () => { result = false; }, { signal: controller.signal }); + controller.abort(); + target.dispatchEvent(new CustomEvent('test')); + + return result; +} + +/** + * Resolves the closest HTMLElement ancestor of an event's target. + */ +export function eventTarget(event: Event): HTMLElement { + if (event.target instanceof HTMLElement) { + return event.target; + } else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) { + return event.target.parentElement ?? document.body; + } else { + return document.body; + } +} + +/*** + This technique for proper load detection is taken from HTMX: + + BSD 2-Clause License + + Copyright (c) 2020, Big Sky Software + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***/ + +let isReady = false; +document.addEventListener('DOMContentLoaded', () => { isReady = true }); + +export function whenReady(callback: () => void) { + if (isReady || document.readyState === 'complete') { + callback(); + } else { + document.addEventListener('DOMContentLoaded', callback); + } +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts new file mode 100644 index 000000000..11c9c8b52 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts @@ -0,0 +1,528 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import {newRuntimeCaller, objectNames} from "./runtime.js"; +import type { Screen } from "./screens.js"; + +const PositionMethod = 0; +const CenterMethod = 1; +const CloseMethod = 2; +const DisableSizeConstraintsMethod = 3; +const EnableSizeConstraintsMethod = 4; +const FocusMethod = 5; +const ForceReloadMethod = 6; +const FullscreenMethod = 7; +const GetScreenMethod = 8; +const GetZoomMethod = 9; +const HeightMethod = 10; +const HideMethod = 11; +const IsFocusedMethod = 12; +const IsFullscreenMethod = 13; +const IsMaximisedMethod = 14; +const IsMinimisedMethod = 15; +const MaximiseMethod = 16; +const MinimiseMethod = 17; +const NameMethod = 18; +const OpenDevToolsMethod = 19; +const RelativePositionMethod = 20; +const ReloadMethod = 21; +const ResizableMethod = 22; +const RestoreMethod = 23; +const SetPositionMethod = 24; +const SetAlwaysOnTopMethod = 25; +const SetBackgroundColourMethod = 26; +const SetFramelessMethod = 27; +const SetFullscreenButtonEnabledMethod = 28; +const SetMaxSizeMethod = 29; +const SetMinSizeMethod = 30; +const SetRelativePositionMethod = 31; +const SetResizableMethod = 32; +const SetSizeMethod = 33; +const SetTitleMethod = 34; +const SetZoomMethod = 35; +const ShowMethod = 36; +const SizeMethod = 37; +const ToggleFullscreenMethod = 38; +const ToggleMaximiseMethod = 39; +const ToggleFramelessMethod = 40; +const UnFullscreenMethod = 41; +const UnMaximiseMethod = 42; +const UnMinimiseMethod = 43; +const WidthMethod = 44; +const ZoomMethod = 45; +const ZoomInMethod = 46; +const ZoomOutMethod = 47; +const ZoomResetMethod = 48; + +/** + * A record describing the position of a window. + */ +interface Position { + /** The horizontal position of the window. */ + x: number; + /** The vertical position of the window. */ + y: number; +} + +/** + * A record describing the size of a window. + */ +interface Size { + /** The width of the window. */ + width: number; + /** The height of the window. */ + height: number; +} + +// Private field names. +const callerSym = Symbol("caller"); + +class Window { + // Private fields. + private [callerSym]: (message: number, args?: any) => Promise; + + /** + * Initialises a window object with the specified name. + * + * @private + * @param name - The name of the target window. + */ + constructor(name: string = '') { + this[callerSym] = newRuntimeCaller(objectNames.Window, name) + + // bind instance method to make them easily usable in event handlers + for (const method of Object.getOwnPropertyNames(Window.prototype)) { + if ( + method !== "constructor" + && typeof (this as any)[method] === "function" + ) { + (this as any)[method] = (this as any)[method].bind(this); + } + } + } + + /** + * Gets the specified window. + * + * @param name - The name of the window to get. + * @returns The corresponding window object. + */ + Get(name: string): Window { + return new Window(name); + } + + /** + * Returns the absolute position of the window. + * + * @returns The current absolute position of the window. + */ + Position(): Promise { + return this[callerSym](PositionMethod); + } + + /** + * Centers the window on the screen. + */ + Center(): Promise { + return this[callerSym](CenterMethod); + } + + /** + * Closes the window. + */ + Close(): Promise { + return this[callerSym](CloseMethod); + } + + /** + * Disables min/max size constraints. + */ + DisableSizeConstraints(): Promise { + return this[callerSym](DisableSizeConstraintsMethod); + } + + /** + * Enables min/max size constraints. + */ + EnableSizeConstraints(): Promise { + return this[callerSym](EnableSizeConstraintsMethod); + } + + /** + * Focuses the window. + */ + Focus(): Promise { + return this[callerSym](FocusMethod); + } + + /** + * Forces the window to reload the page assets. + */ + ForceReload(): Promise { + return this[callerSym](ForceReloadMethod); + } + + /** + * Switches the window to fullscreen mode. + */ + Fullscreen(): Promise { + return this[callerSym](FullscreenMethod); + } + + /** + * Returns the screen that the window is on. + * + * @returns The screen the window is currently on. + */ + GetScreen(): Promise { + return this[callerSym](GetScreenMethod); + } + + /** + * Returns the current zoom level of the window. + * + * @returns The current zoom level. + */ + GetZoom(): Promise { + return this[callerSym](GetZoomMethod); + } + + /** + * Returns the height of the window. + * + * @returns The current height of the window. + */ + Height(): Promise { + return this[callerSym](HeightMethod); + } + + /** + * Hides the window. + */ + Hide(): Promise { + return this[callerSym](HideMethod); + } + + /** + * Returns true if the window is focused. + * + * @returns Whether the window is currently focused. + */ + IsFocused(): Promise { + return this[callerSym](IsFocusedMethod); + } + + /** + * Returns true if the window is fullscreen. + * + * @returns Whether the window is currently fullscreen. + */ + IsFullscreen(): Promise { + return this[callerSym](IsFullscreenMethod); + } + + /** + * Returns true if the window is maximised. + * + * @returns Whether the window is currently maximised. + */ + IsMaximised(): Promise { + return this[callerSym](IsMaximisedMethod); + } + + /** + * Returns true if the window is minimised. + * + * @returns Whether the window is currently minimised. + */ + IsMinimised(): Promise { + return this[callerSym](IsMinimisedMethod); + } + + /** + * Maximises the window. + */ + Maximise(): Promise { + return this[callerSym](MaximiseMethod); + } + + /** + * Minimises the window. + */ + Minimise(): Promise { + return this[callerSym](MinimiseMethod); + } + + /** + * Returns the name of the window. + * + * @returns The name of the window. + */ + Name(): Promise { + return this[callerSym](NameMethod); + } + + /** + * Opens the development tools pane. + */ + OpenDevTools(): Promise { + return this[callerSym](OpenDevToolsMethod); + } + + /** + * Returns the relative position of the window to the screen. + * + * @returns The current relative position of the window. + */ + RelativePosition(): Promise { + return this[callerSym](RelativePositionMethod); + } + + /** + * Reloads the page assets. + */ + Reload(): Promise { + return this[callerSym](ReloadMethod); + } + + /** + * Returns true if the window is resizable. + * + * @returns Whether the window is currently resizable. + */ + Resizable(): Promise { + return this[callerSym](ResizableMethod); + } + + /** + * Restores the window to its previous state if it was previously minimised, maximised or fullscreen. + */ + Restore(): Promise { + return this[callerSym](RestoreMethod); + } + + /** + * Sets the absolute position of the window. + * + * @param x - The desired horizontal absolute position of the window. + * @param y - The desired vertical absolute position of the window. + */ + SetPosition(x: number, y: number): Promise { + return this[callerSym](SetPositionMethod, { x, y }); + } + + /** + * Sets the window to be always on top. + * + * @param alwaysOnTop - Whether the window should stay on top. + */ + SetAlwaysOnTop(alwaysOnTop: boolean): Promise { + return this[callerSym](SetAlwaysOnTopMethod, { alwaysOnTop }); + } + + /** + * Sets the background colour of the window. + * + * @param r - The desired red component of the window background. + * @param g - The desired green component of the window background. + * @param b - The desired blue component of the window background. + * @param a - The desired alpha component of the window background. + */ + SetBackgroundColour(r: number, g: number, b: number, a: number): Promise { + return this[callerSym](SetBackgroundColourMethod, { r, g, b, a }); + } + + /** + * Removes the window frame and title bar. + * + * @param frameless - Whether the window should be frameless. + */ + SetFrameless(frameless: boolean): Promise { + return this[callerSym](SetFramelessMethod, { frameless }); + } + + /** + * Disables the system fullscreen button. + * + * @param enabled - Whether the fullscreen button should be enabled. + */ + SetFullscreenButtonEnabled(enabled: boolean): Promise { + return this[callerSym](SetFullscreenButtonEnabledMethod, { enabled }); + } + + /** + * Sets the maximum size of the window. + * + * @param width - The desired maximum width of the window. + * @param height - The desired maximum height of the window. + */ + SetMaxSize(width: number, height: number): Promise { + return this[callerSym](SetMaxSizeMethod, { width, height }); + } + + /** + * Sets the minimum size of the window. + * + * @param width - The desired minimum width of the window. + * @param height - The desired minimum height of the window. + */ + SetMinSize(width: number, height: number): Promise { + return this[callerSym](SetMinSizeMethod, { width, height }); + } + + /** + * Sets the relative position of the window to the screen. + * + * @param x - The desired horizontal relative position of the window. + * @param y - The desired vertical relative position of the window. + */ + SetRelativePosition(x: number, y: number): Promise { + return this[callerSym](SetRelativePositionMethod, { x, y }); + } + + /** + * Sets whether the window is resizable. + * + * @param resizable - Whether the window should be resizable. + */ + SetResizable(resizable: boolean): Promise { + return this[callerSym](SetResizableMethod, { resizable }); + } + + /** + * Sets the size of the window. + * + * @param width - The desired width of the window. + * @param height - The desired height of the window. + */ + SetSize(width: number, height: number): Promise { + return this[callerSym](SetSizeMethod, { width, height }); + } + + /** + * Sets the title of the window. + * + * @param title - The desired title of the window. + */ + SetTitle(title: string): Promise { + return this[callerSym](SetTitleMethod, { title }); + } + + /** + * Sets the zoom level of the window. + * + * @param zoom - The desired zoom level. + */ + SetZoom(zoom: number): Promise { + return this[callerSym](SetZoomMethod, { zoom }); + } + + /** + * Shows the window. + */ + Show(): Promise { + return this[callerSym](ShowMethod); + } + + /** + * Returns the size of the window. + * + * @returns The current size of the window. + */ + Size(): Promise { + return this[callerSym](SizeMethod); + } + + /** + * Toggles the window between fullscreen and normal. + */ + ToggleFullscreen(): Promise { + return this[callerSym](ToggleFullscreenMethod); + } + + /** + * Toggles the window between maximised and normal. + */ + ToggleMaximise(): Promise { + return this[callerSym](ToggleMaximiseMethod); + } + + /** + * Toggles the window between frameless and normal. + */ + ToggleFrameless(): Promise { + return this[callerSym](ToggleFramelessMethod); + } + + /** + * Un-fullscreens the window. + */ + UnFullscreen(): Promise { + return this[callerSym](UnFullscreenMethod); + } + + /** + * Un-maximises the window. + */ + UnMaximise(): Promise { + return this[callerSym](UnMaximiseMethod); + } + + /** + * Un-minimises the window. + */ + UnMinimise(): Promise { + return this[callerSym](UnMinimiseMethod); + } + + /** + * Returns the width of the window. + * + * @returns The current width of the window. + */ + Width(): Promise { + return this[callerSym](WidthMethod); + } + + /** + * Zooms the window. + */ + Zoom(): Promise { + return this[callerSym](ZoomMethod); + } + + /** + * Increases the zoom level of the webview content. + */ + ZoomIn(): Promise { + return this[callerSym](ZoomInMethod); + } + + /** + * Decreases the zoom level of the webview content. + */ + ZoomOut(): Promise { + return this[callerSym](ZoomOutMethod); + } + + /** + * Resets the zoom level of the webview content. + */ + ZoomReset(): Promise { + return this[callerSym](ZoomResetMethod); + } +} + +/** + * The window within which the script is running. + */ +const thisWindow = new Window(''); + +export default thisWindow; diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts new file mode 100644 index 000000000..148432d64 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts @@ -0,0 +1,209 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import { OpenURL } from "./browser.js"; +import { Question } from "./dialogs.js"; +import { Emit } from "./events.js"; +import { canAbortListeners, whenReady } from "./utils.js"; +import Window from "./window.js"; + +/** + * Sends an event with the given name and optional data. + * + * @param eventName - - The name of the event to send. + * @param [data=null] - - Optional data to send along with the event. + */ +function sendEvent(eventName: string, data: any = null): void { + void Emit(eventName, data); +} + +/** + * Calls a method on a specified window. + * + * @param windowName - The name of the window to call the method on. + * @param methodName - The name of the method to call. + */ +function callWindowMethod(windowName: string, methodName: string) { + const targetWindow = Window.Get(windowName); + const method = (targetWindow as any)[methodName]; + + if (typeof method !== "function") { + console.error(`Window method '${methodName}' not found`); + return; + } + + try { + method.call(targetWindow); + } catch (e) { + console.error(`Error calling window method '${methodName}': `, e); + } +} + +/** + * Responds to a triggering event by running appropriate WML actions for the current target. + */ +function onWMLTriggered(ev: Event): void { + const element = ev.currentTarget as Element; + + function runEffect(choice = "Yes") { + if (choice !== "Yes") + return; + + const eventType = element.getAttribute('wml-event') || element.getAttribute('data-wml-event'); + const targetWindow = element.getAttribute('wml-target-window') || element.getAttribute('data-wml-target-window') || ""; + const windowMethod = element.getAttribute('wml-window') || element.getAttribute('data-wml-window'); + const url = element.getAttribute('wml-openurl') || element.getAttribute('data-wml-openurl'); + + if (eventType !== null) + sendEvent(eventType); + if (windowMethod !== null) + callWindowMethod(targetWindow, windowMethod); + if (url !== null) + void OpenURL(url); + } + + const confirm = element.getAttribute('wml-confirm') || element.getAttribute('data-wml-confirm'); + + if (confirm) { + Question({ + Title: "Confirm", + Message: confirm, + Detached: false, + Buttons: [ + { Label: "Yes" }, + { Label: "No", IsDefault: true } + ] + }).then(runEffect); + } else { + runEffect(); + } +} + +// Private field names. +const controllerSym = Symbol("controller"); +const triggerMapSym = Symbol("triggerMap"); +const elementCountSym = Symbol("elementCount"); + +/** + * AbortControllerRegistry does not actually remember active event listeners: instead + * it ties them to an AbortSignal and uses an AbortController to remove them all at once. + */ +class AbortControllerRegistry { + // Private fields. + [controllerSym]: AbortController; + + constructor() { + this[controllerSym] = new AbortController(); + } + + /** + * Returns an options object for addEventListener that ties the listener + * to the AbortSignal from the current AbortController. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified elements + */ + set(element: Element, triggers: string[]): AddEventListenerOptions { + return { signal: this[controllerSym].signal }; + } + + /** + * Removes all registered event listeners and resets the registry. + */ + reset(): void { + this[controllerSym].abort(); + this[controllerSym] = new AbortController(); + } +} + +/** + * WeakMapRegistry maps active trigger events to each DOM element through a WeakMap. + * This ensures that the mapping remains private to this module, while still allowing garbage + * collection of the involved elements. + */ +class WeakMapRegistry { + /** Stores the current element-to-trigger mapping. */ + [triggerMapSym]: WeakMap; + /** Counts the number of elements with active WML triggers. */ + [elementCountSym]: number; + + constructor() { + this[triggerMapSym] = new WeakMap(); + this[elementCountSym] = 0; + } + + /** + * Sets active triggers for the specified element. + * + * @param element - An HTML element + * @param triggers - The list of active WML trigger events for the specified element + */ + set(element: Element, triggers: string[]): AddEventListenerOptions { + if (!this[triggerMapSym].has(element)) { this[elementCountSym]++; } + this[triggerMapSym].set(element, triggers); + return {}; + } + + /** + * Removes all registered event listeners. + */ + reset(): void { + if (this[elementCountSym] <= 0) + return; + + for (const element of document.body.querySelectorAll('*')) { + if (this[elementCountSym] <= 0) + break; + + const triggers = this[triggerMapSym].get(element); + if (triggers != null) { this[elementCountSym]--; } + + for (const trigger of triggers || []) + element.removeEventListener(trigger, onWMLTriggered); + } + + this[triggerMapSym] = new WeakMap(); + this[elementCountSym] = 0; + } +} + +const triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry(); + +/** + * Adds event listeners to the specified element. + */ +function addWMLListeners(element: Element): void { + const triggerRegExp = /\S+/g; + const triggerAttr = (element.getAttribute('wml-trigger') || element.getAttribute('data-wml-trigger') || "click"); + const triggers: string[] = []; + + let match; + while ((match = triggerRegExp.exec(triggerAttr)) !== null) + triggers.push(match[0]); + + const options = triggerRegistry.set(element, triggers); + for (const trigger of triggers) + element.addEventListener(trigger, onWMLTriggered, options); +} + +/** + * Schedules an automatic reload of WML to be performed as soon as the document is fully loaded. + */ +export function Enable(): void { + whenReady(Reload); +} + +/** + * Reloads the WML page by adding necessary event listeners and browser listeners. + */ +export function Reload(): void { + triggerRegistry.reset(); + document.body.querySelectorAll('[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]').forEach(addWMLListeners); +} diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json b/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json new file mode 100644 index 000000000..1175a4af4 --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json @@ -0,0 +1,36 @@ +{ + "include": ["./src/**/*"], + "exclude": ["./src/**/*.test.*"], + "compilerOptions": { + "composite": true, + + "allowJs": false, + + "noEmitOnError": true, + "declaration": true, + "declarationMap": false, + "declarationDir": "types", + "outDir": "dist", + "rootDir": "src", + + "target": "ES2017", + "module": "ES2015", + "moduleResolution": "bundler", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "stripInternal": true, + + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts b/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts new file mode 100644 index 000000000..efb60170a --- /dev/null +++ b/v3/internal/runtime/desktop/@wailsio/runtime/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: 'happy-dom', + testTimeout: 200 + }, +}); diff --git a/v3/internal/runtime/desktop/README.md b/v3/internal/runtime/desktop/README.md new file mode 100644 index 000000000..0ca8ed53e --- /dev/null +++ b/v3/internal/runtime/desktop/README.md @@ -0,0 +1,10 @@ +# README + +The `index.js` file in the `compiled` directory is the entrypoint for the `runtime.js` file that may be +loaded at runtime. This will add `window.wails` and `window._wails` to the global scope. + +NOTE: It is preferable to use the `@wailsio/runtime` package to use the runtime. + +โš ๏ธ Do not rebuild the runtime manually after updating TS code: +the CI pipeline will take care of this. +PRs that touch build artifacts will be blocked from merging. \ No newline at end of file diff --git a/v3/internal/runtime/desktop/compiled/main.js b/v3/internal/runtime/desktop/compiled/main.js new file mode 100644 index 000000000..d2b21dca1 --- /dev/null +++ b/v3/internal/runtime/desktop/compiled/main.js @@ -0,0 +1,22 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +import * as Runtime from "../@wailsio/runtime/src"; + +// NOTE: the following methods MUST be imported explicitly because of how esbuild injection works +import { Enable as EnableWML } from "../@wailsio/runtime/src/wml"; +import { debugLog } from "../@wailsio/runtime/src/utils"; + +window.wails = Runtime; +EnableWML(); + +if (DEBUG) { + debugLog("Wails Runtime Loaded") +} diff --git a/v3/internal/runtime/runtime.go b/v3/internal/runtime/runtime.go new file mode 100644 index 000000000..eee7f230c --- /dev/null +++ b/v3/internal/runtime/runtime.go @@ -0,0 +1,7 @@ +package runtime + +var runtimeInit = `window._wails=window._wails||{};window.wails=window.wails||{};` + +func Core() string { + return runtimeInit + flags + invoke + environment +} diff --git a/v3/internal/runtime/runtime_darwin.go b/v3/internal/runtime/runtime_darwin.go new file mode 100644 index 000000000..f40c93d3e --- /dev/null +++ b/v3/internal/runtime/runtime_darwin.go @@ -0,0 +1,6 @@ +//go:build darwin + +package runtime + +var invoke = "window._wails.invoke=function(msg){window.webkit.messageHandlers.external.postMessage(msg);};" +var flags = "" diff --git a/v3/internal/runtime/runtime_dev.go b/v3/internal/runtime/runtime_dev.go new file mode 100644 index 000000000..bb52628cf --- /dev/null +++ b/v3/internal/runtime/runtime_dev.go @@ -0,0 +1,10 @@ +//go:build !production + +package runtime + +import ( + "fmt" + "runtime" +) + +var environment = fmt.Sprintf(`window._wails.environment={"OS":"%s","Arch":"%s","Debug":true};`, runtime.GOOS, runtime.GOARCH) diff --git a/v3/internal/runtime/runtime_linux.go b/v3/internal/runtime/runtime_linux.go new file mode 100644 index 000000000..7d9f32569 --- /dev/null +++ b/v3/internal/runtime/runtime_linux.go @@ -0,0 +1,6 @@ +//go:build linux + +package runtime + +var invoke = "window._wails.invoke=window.webkit.messageHandlers.external.postMessage;" +var flags = "" diff --git a/v3/internal/runtime/runtime_prod.go b/v3/internal/runtime/runtime_prod.go new file mode 100644 index 000000000..ec3613551 --- /dev/null +++ b/v3/internal/runtime/runtime_prod.go @@ -0,0 +1,8 @@ +//go:build production + +package runtime + +import "fmt" +import goruntime "runtime" + +var environment = fmt.Sprintf(`window._wails.environment={"OS":"%s","Arch":"%s","Debug":false};`, goruntime.GOOS, goruntime.GOARCH) diff --git a/v3/internal/runtime/runtime_windows.go b/v3/internal/runtime/runtime_windows.go new file mode 100644 index 000000000..f706aa3b1 --- /dev/null +++ b/v3/internal/runtime/runtime_windows.go @@ -0,0 +1,14 @@ +//go:build windows + +package runtime + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var invoke = `window._wails.invoke=window.chrome.webview.postMessage;` +var flags = fmt.Sprintf( + `window._wails.flags={"system":{"resizeHandleWidth":%d,"resizeHandleHeight":%d}};`, + w32.GetSystemMetrics(w32.SM_CXSIZEFRAME), + w32.GetSystemMetrics(w32.SM_CYSIZEFRAME)) diff --git a/v3/internal/s/s.go b/v3/internal/s/s.go new file mode 100644 index 000000000..1ce187e07 --- /dev/null +++ b/v3/internal/s/s.go @@ -0,0 +1,456 @@ +package s + +import ( + "crypto/md5" + "fmt" + "github.com/google/shlex" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" +) + +var ( + Output io.Writer = io.Discard + IndentSize int + originalOutput io.Writer + currentIndent int + dryRun bool + deferred []func() +) + +func checkError(err error) { + if err != nil { + println("\nERROR:", err.Error()) + os.Exit(1) + } +} + +func mute() { + originalOutput = Output + Output = io.Discard +} + +func unmute() { + Output = originalOutput +} + +func indent() { + currentIndent += IndentSize +} + +func unindent() { + currentIndent -= IndentSize +} + +func log(message string, args ...interface{}) { + indent := strings.Repeat(" ", currentIndent) + _, err := fmt.Fprintf(Output, indent+message+"\n", args...) + checkError(err) +} + +// RENAME a file or directory +func RENAME(source string, target string) { + log("RENAME %s -> %s", source, target) + err := os.Rename(source, target) + checkError(err) +} + +// MUSTDELETE a file. +func MUSTDELETE(filename string) { + log("DELETE %s", filename) + err := os.Remove(filepath.Join(CWD(), filename)) + checkError(err) +} + +// DELETE a file. +func DELETE(filename string) { + log("DELETE %s", filename) + _ = os.Remove(filepath.Join(CWD(), filename)) +} + +func CONTAINS(list string, item string) bool { + result := strings.Contains(list, item) + listTrimmed := list + if len(listTrimmed) > 30 { + listTrimmed = listTrimmed[:30] + "..." + } + log("CONTAINS %s in %s: %t", item, listTrimmed, result) + return result +} + +func SETENV(key string, value string) { + log("SETENV %s=%s", key, value) + err := os.Setenv(key, value) + checkError(err) +} + +func CD(dir string) { + err := os.Chdir(dir) + checkError(err) + log("CD %s", dir) +} +func MKDIR(path string, mode ...os.FileMode) { + var perms os.FileMode + perms = 0755 + if len(mode) == 1 { + perms = mode[0] + } + log("MKDIR %s (perms: %v)", path, perms) + err := os.MkdirAll(path, perms) + checkError(err) +} + +// ENDIR ensures that the path gets created if it doesn't exist +func ENDIR(path string, mode ...os.FileMode) { + var perms os.FileMode + perms = 0755 + if len(mode) == 1 { + perms = mode[0] + } + _ = os.MkdirAll(path, perms) +} + +// COPYDIR recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +// Symlinks are ignored and skipped. +// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 +func COPYDIR(src string, dst string) { + log("COPYDIR %s -> %s", src, dst) + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + checkError(err) + if !si.IsDir() { + checkError(fmt.Errorf("source is not a directory")) + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + checkError(err) + } + if err == nil { + checkError(fmt.Errorf("destination already exists")) + } + + indent() + MKDIR(dst) + + entries, err := os.ReadDir(src) + checkError(err) + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + COPYDIR(srcPath, dstPath) + } else { + // Skip symlinks. + if entry.Type()&os.ModeSymlink != 0 { + continue + } + + COPY(srcPath, dstPath) + } + } + unindent() +} + +// COPYDIR2 recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory can exist. +// Symlinks are ignored and skipped. +// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 +func COPYDIR2(src string, dst string) { + log("COPYDIR %s -> %s", src, dst) + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + checkError(err) + if !si.IsDir() { + checkError(fmt.Errorf("source is not a directory")) + } + + indent() + MKDIR(dst) + + entries, err := os.ReadDir(src) + checkError(err) + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + COPYDIR(srcPath, dstPath) + } else { + // Skip symlinks. + if entry.Type()&os.ModeSymlink != 0 { + continue + } + + COPY(srcPath, dstPath) + } + } + unindent() +} + +func SYMLINK(source string, target string) { + // trim string to first 30 chars + var trimTarget = target + if len(trimTarget) > 30 { + trimTarget = trimTarget[:30] + "..." + } + log("SYMLINK %s -> %s", source, trimTarget) + err := os.Symlink(source, target) + checkError(err) +} + +// COPY file from source to target +func COPY(source string, target string) { + log("COPY %s -> %s", source, target) + src, err := os.Open(source) + checkError(err) + defer closefile(src) + if ISDIR(target) { + target = filepath.Join(target, filepath.Base(source)) + } + d, err := os.Create(target) + checkError(err) + _, err = io.Copy(d, src) + checkError(err) +} + +// Move file from source to target +func MOVE(source string, target string) { + // If target is a directory, append the source filename + if ISDIR(target) { + target = filepath.Join(target, filepath.Base(source)) + } + log("MOVE %s -> %s", source, target) + err := os.Rename(source, target) + checkError(err) +} + +func CWD() string { + result, err := os.Getwd() + checkError(err) + log("CWD %s", result) + return result +} + +func RMDIR(target string) { + log("RMDIR %s", target) + err := os.RemoveAll(target) + checkError(err) +} + +func RM(target string) { + log("RM %s", target) + err := os.Remove(target) + checkError(err) +} + +func ECHO(message string) { + println(message) +} + +func TOUCH(filepath string) { + log("TOUCH %s", filepath) + f, err := os.Create(filepath) + checkError(err) + closefile(f) +} + +func EXEC(command string) ([]byte, error) { + log("EXEC %s", command) + + // Split input using shlex + args, err := shlex.Split(command) + checkError(err) + // Execute command + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = CWD() + cmd.Env = os.Environ() + return cmd.CombinedOutput() +} + +func CHMOD(path string, mode os.FileMode) { + log("CHMOD %s %v", path, mode) + err := os.Chmod(path, mode) + checkError(err) +} + +// EXISTS - Returns true if the given path exists +func EXISTS(path string) bool { + _, err := os.Lstat(path) + log("EXISTS %s -> %t", path, err == nil) + return err == nil +} + +// ISDIR returns true if the given directory exists +func ISDIR(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsDir() +} + +// ISDIREMPTY returns true if the given directory is empty +func ISDIREMPTY(dir string) bool { + + // CREDIT: https://stackoverflow.com/a/30708914/8325411 + f, err := os.Open(dir) + checkError(err) + defer closefile(f) + + _, err = f.Readdirnames(1) // Or f.Readdir(1) + if err == io.EOF { + return true + } + return false +} + +// ISFILE returns true if the given file exists +func ISFILE(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + +// SUBDIRS returns a list of subdirectories for the given directory +func SUBDIRS(rootDir string) []string { + var result []string + + // Iterate root dir + err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { + checkError(err) + // If we have a directory, save it + if info.IsDir() { + result = append(result, path) + } + return nil + }) + checkError(err) + return result +} + +// SAVESTRING will create a file with the given string +func SAVESTRING(filename string, data string) { + log("SAVESTRING %s", filename) + mute() + SAVEBYTES(filename, []byte(data)) + unmute() +} + +// LOADSTRING returns the contents of the given filename as a string +func LOADSTRING(filename string) string { + log("LOADSTRING %s", filename) + mute() + data := LOADBYTES(filename) + unmute() + return string(data) +} + +// SAVEBYTES will create a file with the given string +func SAVEBYTES(filename string, data []byte) { + log("SAVEBYTES %s", filename) + err := os.WriteFile(filename, data, 0755) + checkError(err) +} + +// LOADBYTES returns the contents of the given filename as a string +func LOADBYTES(filename string) []byte { + log("LOADBYTES %s", filename) + data, err := os.ReadFile(filename) + checkError(err) + return data +} + +func closefile(f *os.File) { + err := f.Close() + checkError(err) +} + +// MD5FILE returns the md5sum of the given file +func MD5FILE(filename string) string { + f, err := os.Open(filename) + checkError(err) + defer closefile(f) + + h := md5.New() + _, err = io.Copy(h, f) + checkError(err) + + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// Sub is the substitution type +type Sub map[string]string + +// REPLACEALL replaces all substitution keys with associated values in the given file +func REPLACEALL(filename string, substitutions Sub) { + log("REPLACEALL %s (%v)", filename, substitutions) + data := LOADSTRING(filename) + for old, newText := range substitutions { + data = strings.ReplaceAll(data, old, newText) + } + SAVESTRING(filename, data) +} + +func DOWNLOAD(url string, target string) { + log("DOWNLOAD %s -> %s", url, target) + // create HTTP client + resp, err := http.Get(url) + checkError(err) + defer resp.Body.Close() + + out, err := os.Create(target) + checkError(err) + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + checkError(err) +} + +func FINDFILES(root string, filenames ...string) []string { + var result []string + // Walk the root directory trying to find all the files + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + checkError(err) + // If we have a file, check if it is in the list + if info.Mode().IsRegular() { + for _, filename := range filenames { + if info.Name() == filename { + result = append(result, path) + } + } + } + return nil + }) + checkError(err) + log("FINDFILES in %s -> [%v]", root, strings.Join(result, ", ")) + return result +} + +func DEFER(fn func()) { + log("DEFER") + deferred = append(deferred, fn) +} + +func CALLDEFER() { + log("CALLDEFER") + for _, fn := range deferred { + fn() + } +} diff --git a/v3/internal/service/service.go b/v3/internal/service/service.go new file mode 100644 index 000000000..76c1c2560 --- /dev/null +++ b/v3/internal/service/service.go @@ -0,0 +1,36 @@ +package service + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed template +var serviceTemplate embed.FS + +type TemplateOptions struct { + *flags.ServiceInit +} + +func Install(options *flags.ServiceInit) error { + + if options.OutputDir == "." || options.OutputDir == "" { + options.OutputDir = filepath.Join(lo.Must(os.Getwd()), options.Name) + } + fmt.Printf("Generating service '%s' into '%s'\n", options.Name, options.OutputDir) + tfs, err := fs.Sub(serviceTemplate, "template") + if err != nil { + return err + } + + return gosod.New(tfs).Extract(options.OutputDir, options) +} diff --git a/v3/internal/service/template/README.tmpl.md b/v3/internal/service/template/README.tmpl.md new file mode 100644 index 000000000..065021b6a --- /dev/null +++ b/v3/internal/service/template/README.tmpl.md @@ -0,0 +1,129 @@ +# Wails v3 Service Template + +This README provides an overview of the Wails v3 service template and explains how to adapt it to create your own custom service. + +## Overview + +The service template provides a basic structure for creating a Wails v3 service. A service in Wails v3 is a Go package that can be integrated into your Wails application to provide specific functionality, handle HTTP requests, and interact with the frontend. + +## Template Structure + +The template defines a `MyService` struct and several methods: + +### MyService Struct + +```go +type MyService struct { + ctx context.Context + options application.ServiceOptions +} +``` + +This is the main service struct. You can rename it to better reflect your service's purpose. The struct holds a context and service options, which are set during startup. + +### ServiceName Method + +```go +func (p *MyService) ServiceName() string +``` + +This method returns the name of the service. It's used to identify the service within the Wails application. + +### ServiceStartup Method + +```go +func (p *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error +``` + +This method is called when the app is starting up. Use it to initialize resources, set up connections, or perform any necessary setup tasks. +It receives a context and service options, which are stored in the service struct. + +### ServiceShutdown Method + +```go +func (p *MyService) ServiceShutdown() error +``` + +This method is called when the app is shutting down. Use it to clean up resources, close connections, or perform any necessary cleanup tasks. + +### ServeHTTP Method + +```go +func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request) +``` + +This method handles HTTP requests to the service. It's called when the frontend makes an HTTP request to the backend +at the path specified in the `Route` field of the service options. + +### Service Methods + +```go +func (p *MyService) Greet(name string) string +``` + +This is an example of a service method. You can add as many methods as you need. These methods can be called from the frontend. + +## Adapting the Template + +To create your own service: + +1. Rename the `MyService` struct to reflect your service's purpose (e.g., `DatabaseService`, `AuthService`). +2. Update the `ServiceName` method to return your service's unique identifier. +3. Implement the `ServiceStartup` method to initialize your service. This might include setting up database connections, loading configuration, etc. +4. If needed, implement the `ServiceShutdown` method to properly clean up resources when the application closes. +5. If your service needs to handle HTTP requests, implement the `ServeHTTP` method. Use this to create API endpoints, serve files, or handle any HTTP interactions. +6. Add your own methods to the service. These can include database operations, business logic, or any functionality your service needs to provide. +7. If your service requires configuration, consider adding a `Config` struct and a `New` function to create and configure your service. + +## Example: Database Service + +Here's how you might adapt the template for a database service: + +```go +type DatabaseService struct { + ctx context.Context + options application.ServiceOptions + db *sql.DB +} + +func (s *DatabaseService) Name() string { + return "github.com/myname/DatabaseService" +} + +func (s *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + s.ctx = ctx + s.options = options + // Initialize database connection + var err error + s.db, err = sql.Open("mysql", "user:password@/dbname") + return err +} + +func (s *DatabaseService) ServiceShutdown() error { + return s.db.Close() +} + +func (s *DatabaseService) GetUser(id int) (User, error) { + // Implement database query +} + +// Add more methods as needed +``` + +## Long-running tasks + +If your service needs to perform long-running tasks, consider using goroutines and channels to manage these tasks. +You can use the `context.Context` to listen for when the application shuts down: + +```go +func (s *DatabaseService) longRunningTask() { + for { + select { + case <-s.ctx.Done(): + // Cleanup and exit + return + // Perform long-running task + } + } +} +``` diff --git a/v3/internal/service/template/go.mod.tmpl b/v3/internal/service/template/go.mod.tmpl new file mode 100644 index 000000000..dc8719753 --- /dev/null +++ b/v3/internal/service/template/go.mod.tmpl @@ -0,0 +1,12 @@ +module {{.Name}} + +go 1.23 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.7 + +require ( + github.com/imdario/mergo v0.3.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) diff --git a/v3/internal/service/template/go.sum b/v3/internal/service/template/go.sum new file mode 100644 index 000000000..991eadf04 --- /dev/null +++ b/v3/internal/service/template/go.sum @@ -0,0 +1,20 @@ +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v3 v3.0.0-alpha.7 h1:LNX2EnbxTEYJYICJT8UkuzoGVNalRizTNGBY47endmk= +github.com/wailsapp/wails/v3 v3.0.0-alpha.7/go.mod h1:lBz4zedFxreJBoVpMe9u89oo4IE3IlyHJg5rOWnGNR0= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v3/internal/service/template/service.go.tmpl b/v3/internal/service/template/service.go.tmpl new file mode 100644 index 000000000..781e9efff --- /dev/null +++ b/v3/internal/service/template/service.go.tmpl @@ -0,0 +1,60 @@ +package {{.Name}} + +import ( + "context" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Service Setup ---------------- +// This is the main service struct. It can be named anything you like. +// Both the ServiceStartup() and ServiceShutdown() methods are called synchronously when the app starts and stops. +// Changing the name of this struct will change the name of the services class in the frontend +// Bound methods will exist inside frontend/bindings/github.com/user/{{.Name}} under the name of the struct +type MyService struct{ + ctx context.Context + options application.ServiceOptions +} + +// ServiceName is the name of the service +func (p *MyService) ServiceName() string { + return "{{.Name}}" +} + +// ServiceStartup is called when the app is starting up. You can use this to +// initialise any resources you need. You can also access the application +// instance via the app property. +// OPTIONAL: This method is optional. +func (p *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + p.ctx = ctx + p.options = options + return nil +} + +// ServiceShutdown is called when the app is shutting down via runtime.Quit() call +// You can use this to clean up any resources you have allocated +// OPTIONAL: This method is optional. +func (p *MyService) ServiceShutdown() error { + return nil +} + +// ServeHTTP is called when the app is running and the frontend makes an HTTP request to the backend at the path +// specified in the `Route` field of the service Options. +// OPTIONAL: This method is optional. +func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // You can use the request to get the path, query parameters, headers, etc. + // You can also use the response to set the status code, headers, body etc. + // Consult the net/http documentation for more information: https://pkg.go.dev/net/http + + // Log the request to the console + log.Printf("Received request: %s %s", r.Method, r.URL.Path) +} + +// ---------------- Service Methods ---------------- +// Service methods are just normal Go methods. You can add as many as you like. +// The only requirement is that they are exported (start with a capital letter). +// You can also return any type that is JSON serializable. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *MyService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/service/template/service.tmpl.yml b/v3/internal/service/template/service.tmpl.yml new file mode 100644 index 000000000..bd018461e --- /dev/null +++ b/v3/internal/service/template/service.tmpl.yml @@ -0,0 +1,8 @@ +# This is the plugin definition file for the "{{.Name}}" plugin. +Name: "{{.Name}}" +Description: "{{.Description}}" +Author: "{{.Author}}" +Version: "{{.Version}}" +Website: "{{.Website}}" +Repository: "{{.Repository}}" +License: "{{.License}}" diff --git a/v3/internal/signal/signal.go b/v3/internal/signal/signal.go new file mode 100644 index 000000000..8ac9821d0 --- /dev/null +++ b/v3/internal/signal/signal.go @@ -0,0 +1,50 @@ +package signal + +import ( + "fmt" + "log/slog" + "os" + "os/signal" + "syscall" +) + +type SignalHandler struct { + cleanup func() + ExitMessage func(sig os.Signal) string + MaxSignal int + Logger *slog.Logger + LogLevel slog.Level +} + +func NewSignalHandler(cleanup func()) *SignalHandler { + return &SignalHandler{ + cleanup: cleanup, + ExitMessage: func(sig os.Signal) string { return fmt.Sprintf("Received signal: %v. Quitting...\n", sig) }, + MaxSignal: 3, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), + LogLevel: slog.LevelInfo, + } +} + +func (s *SignalHandler) Start() { + ctrlC := make(chan os.Signal, s.MaxSignal) + signal.Notify(ctrlC, os.Interrupt, syscall.SIGTERM) + + go func() { + for i := 1; i <= s.MaxSignal; i++ { + sig := <-ctrlC + + if i == 1 { + s.Logger.Info(s.ExitMessage(sig)) + s.cleanup() + break + } else if i < s.MaxSignal { + s.Logger.Info(fmt.Sprintf("Received signal: %v. Press CTRL+C %d more times to force quit...\n", sig, s.MaxSignal-i)) + continue + } else { + s.Logger.Info(fmt.Sprintf("Received signal: %v. Force quitting...\n", sig)) + os.Exit(1) + } + } + }() +} diff --git a/v3/internal/templates/_common/README.md b/v3/internal/templates/_common/README.md new file mode 100644 index 000000000..ad12c3f40 --- /dev/null +++ b/v3/internal/templates/_common/README.md @@ -0,0 +1,59 @@ +# Welcome to Your New Wails3 Project! + +Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running. + +## Getting Started + +1. Navigate to your project directory in the terminal. + +2. To run your application in development mode, use the following command: + + ``` + wails3 dev + ``` + + This will start your application and enable hot-reloading for both frontend and backend changes. + +3. To build your application for production, use: + + ``` + wails3 build + ``` + + This will create a production-ready executable in the `build` directory. + +## Exploring Wails3 Features + +Now that you have your project set up, it's time to explore the features that Wails3 offers: + +1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications. + +2. **Run an example**: To run any of the examples, navigate to the example's directory and use: + + ``` + go run . + ``` + + Note: Some examples may be under development during the alpha phase. + +3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references. + +4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions). + +## Project Structure + +Take a moment to familiarize yourself with your project structure: + +- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript) +- `main.go`: The entry point of your Go backend +- `app.go`: Define your application structure and methods here +- `wails.json`: Configuration file for your Wails project + +## Next Steps + +1. Modify the frontend in the `frontend/` directory to create your desired UI. +2. Add backend functionality in `main.go`. +3. Use `wails3 dev` to see your changes in real-time. +4. When ready, build your application with `wails3 build`. + +Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community. diff --git a/v3/internal/templates/_common/Taskfile.tmpl.yml b/v3/internal/templates/_common/Taskfile.tmpl.yml new file mode 100644 index 000000000..f075aa3ea --- /dev/null +++ b/v3/internal/templates/_common/Taskfile.tmpl.yml @@ -0,0 +1,34 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "{{.ProjectName}}" + BIN_DIR: "bin" + VITE_PORT: {{ "'{{.WAILS_VITE_PORT | default 9245}}'" }} + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{ "{{OS}}" }}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{ "{{OS}}" }}:package" + + run: + summary: Runs the application + cmds: + - task: "{{ "{{OS}}" }}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{ "{{.VITE_PORT}}" }} + diff --git a/v3/internal/templates/_common/frontend/Inter Font License.txt b/v3/internal/templates/_common/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/v3/internal/templates/_common/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v3/internal/templates/_common/gitignore.tmpl b/v3/internal/templates/_common/gitignore.tmpl new file mode 100644 index 000000000..ba8194ab6 --- /dev/null +++ b/v3/internal/templates/_common/gitignore.tmpl @@ -0,0 +1,6 @@ +.task +bin +frontend/dist +frontend/node_modules +build/linux/appimage/build +build/windows/nsis/MicrosoftEdgeWebview2Setup.exe \ No newline at end of file diff --git a/v3/internal/templates/_common/go.mod.tmpl b/v3/internal/templates/_common/go.mod.tmpl new file mode 100644 index 000000000..bc6a656d1 --- /dev/null +++ b/v3/internal/templates/_common/go.mod.tmpl @@ -0,0 +1,51 @@ +module changeme + +go 1.24 + +require github.com/wailsapp/wails/v3 {{.WailsVersion}} + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.19 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) +{{if .LocalModulePath}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 +{{end}} diff --git a/v3/internal/templates/_common/go.sum.tmpl b/v3/internal/templates/_common/go.sum.tmpl new file mode 100644 index 000000000..977b11504 --- /dev/null +++ b/v3/internal/templates/_common/go.sum.tmpl @@ -0,0 +1,146 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU= +github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/internal/templates/_common/greetservice.go b/v3/internal/templates/_common/greetservice.go new file mode 100644 index 000000000..8972c39cd --- /dev/null +++ b/v3/internal/templates/_common/greetservice.go @@ -0,0 +1,7 @@ +package main + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name + "!" +} diff --git a/v3/internal/templates/_common/main.go.tmpl b/v3/internal/templates/_common/main.go.tmpl new file mode 100644 index 000000000..be9310930 --- /dev/null +++ b/v3/internal/templates/_common/main.go.tmpl @@ -0,0 +1,77 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Wails uses Go's `embed` package to embed the frontend files into the binary. +// Any files in the frontend/dist folder will be embedded into the binary and +// made available to the frontend. +// See https://pkg.go.dev/embed for more information. + +//go:embed all:frontend/dist +var assets embed.FS + +// main function serves as the application's entry point. It initializes the application, creates a window, +// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and +// logs any error that might occur. +func main() { + + // Create a new Wails application by providing the necessary options. + // Variables 'Name' and 'Description' are for application metadata. + // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files. + // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances. + // 'Mac' options tailor the application when running an macOS. + app := application.New(application.Options{ + Name: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a new window with the necessary options. + // 'Title' is the title of the window. + // 'Mac' options tailor the window when running on macOS. + // 'BackgroundColour' is the background colour of the window. + // 'URL' is the URL that will be loaded into the webview. + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window 1", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + BackgroundColour: application.NewRGB(27, 38, 54), + URL: "/", + }) + + // Create a goroutine that emits an event containing the current time every second. + // The frontend can listen to this event and update the UI accordingly. + go func() { + for { + now := time.Now().Format(time.RFC1123) + app.Event.Emit("time", now) + time.Sleep(time.Second) + } + }() + + // Run the application. This blocks until the application has been exited. + err := app.Run() + + // If an error occurred while running the application, log it and exit. + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/base/NEXTSTEPS.md b/v3/internal/templates/base/NEXTSTEPS.md new file mode 100644 index 000000000..6b2e29a08 --- /dev/null +++ b/v3/internal/templates/base/NEXTSTEPS.md @@ -0,0 +1,3 @@ +# Next Steps + +For a full guide on how to create templates, see [Creating Custom Templates](https://v3.wails.io/guides/custom-templates). \ No newline at end of file diff --git a/v3/internal/templates/base/frontend/.gitignore b/v3/internal/templates/base/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/base/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/base/frontend/index.html b/v3/internal/templates/base/frontend/index.html new file mode 100644 index 000000000..b81d9729f --- /dev/null +++ b/v3/internal/templates/base/frontend/index.html @@ -0,0 +1,35 @@ + + + + + + + + Wails App + + +
+ +

Wails + Javascript

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

Wails + Lit

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

Wails + Lit

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

Wails + Preact

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

Click on the Wails logo to learn more

+

{time}

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

Wails + Preact

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

Click on the Wails logo to learn more

+

{time}

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

Wails + Qwik

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

Wails + Qwik

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

Wails + React

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

Click on the Wails logo to learn more

+

{time}

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

Wails + React

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

Click on the Wails logo to learn more

+

{time}

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

Wails + React

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

Click on the Wails logo to learn more

+

{time}

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

Wails + React

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

Click on the Wails logo to learn more

+

{time}

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

Wails + Solid

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

Wails + Solid

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

Click on the Wails logo to learn more

+

{time()}

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

Wails + Svelte

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

Wails + Svelte

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

Wails + Svelte

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

Wails + Svelte

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

Wails + Typescript

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

Wails + Javascript

+
+
Please enter your name below ๐Ÿ‘‡
+
+ + +
+
+ +
+ + + diff --git a/v3/internal/templates/vanilla/frontend/main.js b/v3/internal/templates/vanilla/frontend/main.js new file mode 100644 index 000000000..c24b3b1ef --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/main.js @@ -0,0 +1,21 @@ +import {GreetService} from "./bindings/changeme"; +import {Events} from "@wailsio/runtime"; + +const resultElement = document.getElementById('result'); +const timeElement = document.getElementById('time'); + +window.doGreet = () => { + let name = document.getElementById('name').value; + if (!name) { + name = 'anonymous'; + } + GreetService.Greet(name).then((result) => { + resultElement.innerText = result; + }).catch((err) => { + console.log(err); + }); +} + +Events.On('time', (time) => { + timeElement.innerText = time.data; +}); diff --git a/v3/internal/templates/vanilla/frontend/package.json b/v3/internal/templates/vanilla/frontend/package.json new file mode 100644 index 000000000..0a118e984 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vanilla/frontend/public/javascript.svg b/v3/internal/templates/vanilla/frontend/public/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/public/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/style.css b/v3/internal/templates/vanilla/frontend/public/style.css new file mode 100644 index 000000000..0b9c58279 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/wails.png b/v3/internal/templates/vanilla/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla/template.json b/v3/internal/templates/vanilla/template.json new file mode 100644 index 000000000..5f8a281a5 --- /dev/null +++ b/v3/internal/templates/vanilla/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vanilla + Vite", + "shortname": "vanilla", + "author": "Lea Anthony", + "description": "Vanilla + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/.vscode/extensions.json b/v3/internal/templates/vue-ts/.vscode/extensions.json new file mode 100644 index 000000000..86c616f02 --- /dev/null +++ b/v3/internal/templates/vue-ts/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["octref.vetur"] +} diff --git a/v3/internal/templates/vue-ts/frontend/.gitignore b/v3/internal/templates/vue-ts/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue-ts/frontend/README.md b/v3/internal/templates/vue-ts/frontend/README.md new file mode 100644 index 000000000..ef72fd524 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/v3/internal/templates/vue-ts/frontend/package.json b/v3/internal/templates/vue-ts/frontend/package.json new file mode 100644 index 000000000..dc08ffa11 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vue-tsc && vite build --minify false --mode development", + "build": "vue-tsc && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "typescript": "^4.9.3", + "vite": "^5.0.0", + "vue-tsc": "^1.0.11" + } +} diff --git a/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vue-ts/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vue-ts/frontend/public/style.css b/v3/internal/templates/vue-ts/frontend/public/style.css new file mode 100644 index 000000000..187e7e4b9 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/public/style.css @@ -0,0 +1,145 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + text-align: center; +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/public/vue.svg b/v3/internal/templates/vue-ts/frontend/public/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/public/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/frontend/public/wails.png b/v3/internal/templates/vue-ts/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue-ts/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue-ts/frontend/src/App.vue b/v3/internal/templates/vue-ts/frontend/src/App.vue new file mode 100644 index 000000000..8f62a7e44 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue new file mode 100644 index 000000000..e73e33e23 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue @@ -0,0 +1,47 @@ + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/main.ts b/v3/internal/templates/vue-ts/frontend/src/main.ts new file mode 100644 index 000000000..01433bca2 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/v3/internal/templates/vue-ts/frontend/tsconfig.json b/v3/internal/templates/vue-ts/frontend/tsconfig.json new file mode 100644 index 000000000..8cef1a67b --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/vue-ts/frontend/tsconfig.node.json b/v3/internal/templates/vue-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/v3/internal/templates/vue-ts/frontend/vite.config.ts b/v3/internal/templates/vue-ts/frontend/vite.config.ts new file mode 100644 index 000000000..05c17402a --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/v3/internal/templates/vue-ts/template.json b/v3/internal/templates/vue-ts/template.json new file mode 100644 index 000000000..d87ef872e --- /dev/null +++ b/v3/internal/templates/vue-ts/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vue + Vite (Typescript)", + "shortname": "vue-ts", + "author": "Lea Anthony", + "description": "Vue + TS + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/templates/vue/.vscode/extensions.json b/v3/internal/templates/vue/.vscode/extensions.json new file mode 100644 index 000000000..86c616f02 --- /dev/null +++ b/v3/internal/templates/vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["octref.vetur"] +} diff --git a/v3/internal/templates/vue/frontend/.gitignore b/v3/internal/templates/vue/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/v3/internal/templates/vue/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/internal/templates/vue/frontend/.vscode/extensions.json b/v3/internal/templates/vue/frontend/.vscode/extensions.json new file mode 100644 index 000000000..c0a6e5a48 --- /dev/null +++ b/v3/internal/templates/vue/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/v3/internal/templates/vue/frontend/index.html b/v3/internal/templates/vue/frontend/index.html new file mode 100644 index 000000000..2700f93b9 --- /dev/null +++ b/v3/internal/templates/vue/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Wails + Vue + + +
+ + + diff --git a/v3/internal/templates/vue/frontend/package.json b/v3/internal/templates/vue/frontend/package.json new file mode 100644 index 000000000..6958956b3 --- /dev/null +++ b/v3/internal/templates/vue/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "vite build --minify false --mode development", + "build": "vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^5.0.0" + } +} diff --git a/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf b/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf new file mode 100644 index 000000000..a01f3777a Binary files /dev/null and b/v3/internal/templates/vue/frontend/public/Inter-Medium.ttf differ diff --git a/v3/internal/templates/vue/frontend/public/style.css b/v3/internal/templates/vue/frontend/public/style.css new file mode 100644 index 000000000..187e7e4b9 --- /dev/null +++ b/v3/internal/templates/vue/frontend/public/style.css @@ -0,0 +1,145 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + text-align: center; +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/public/vue.svg b/v3/internal/templates/vue/frontend/public/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue/frontend/public/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vue/frontend/public/wails.png b/v3/internal/templates/vue/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vue/frontend/public/wails.png differ diff --git a/v3/internal/templates/vue/frontend/src/App.vue b/v3/internal/templates/vue/frontend/src/App.vue new file mode 100644 index 000000000..8af6ab35d --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue new file mode 100644 index 000000000..3c7c085c5 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue @@ -0,0 +1,49 @@ + + + diff --git a/v3/internal/templates/vue/frontend/src/main.js b/v3/internal/templates/vue/frontend/src/main.js new file mode 100644 index 000000000..01433bca2 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/main.js @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue/frontend/vite.config.js b/v3/internal/templates/vue/frontend/vite.config.js new file mode 100644 index 000000000..05c17402a --- /dev/null +++ b/v3/internal/templates/vue/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/v3/internal/templates/vue/template.json b/v3/internal/templates/vue/template.json new file mode 100644 index 000000000..b6e8faf93 --- /dev/null +++ b/v3/internal/templates/vue/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vue + Vite", + "shortname": "vue", + "author": "Lea Anthony", + "description": "Vue + Vite development server", + "helpurl": "https://wails.io", + "version": "v0.0.1", + "schema": 3 +} \ No newline at end of file diff --git a/v3/internal/term/term.go b/v3/internal/term/term.go new file mode 100644 index 000000000..9127ff08a --- /dev/null +++ b/v3/internal/term/term.go @@ -0,0 +1,127 @@ +package term + +import ( + "fmt" + "os" + + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/generator/config" + "github.com/wailsapp/wails/v3/internal/version" + "golang.org/x/term" +) + +func Header(header string) { + // Print Wails with the current version in white on red background with the header in white with a green background + pterm.BgLightRed.Print(pterm.LightWhite(" Wails (" + version.String() + ") ")) + pterm.BgLightGreen.Println(pterm.LightWhite(" " + header + " ")) +} + +func IsTerminal() bool { + return term.IsTerminal(int(os.Stdout.Fd())) && (os.Getenv("CI") != "true") +} + +type Spinner struct { + spinner *pterm.SpinnerPrinter +} + +func (s Spinner) Logger() config.Logger { + return config.DefaultPtermLogger(s.spinner) +} + +func StartSpinner(text string) Spinner { + if !IsTerminal() { + return Spinner{} + } + spinner, err := pterm.DefaultSpinner.Start(text) + if err != nil { + return Spinner{} + } + spinner.RemoveWhenDone = true + return Spinner{spinner} +} + +func StopSpinner(s Spinner) { + if s.spinner != nil { + _ = s.spinner.Stop() + } +} + +func output(input any, printer pterm.PrefixPrinter, args ...any) { + switch v := input.(type) { + case string: + printer.Printfln(input.(string), args...) + case error: + printer.Println(v.Error()) + default: + printer.Printfln("%v", v) + } +} + +func Info(input any) { + output(input, pterm.Info) +} + +func Infof(input any, args ...interface{}) { + output(input, pterm.Info, args...) +} + +func Warning(input any) { + output(input, pterm.Warning) +} + +func Warningf(input any, args ...any) { + output(input, pterm.Warning, args...) +} + +func Error(input any) { + output(input, pterm.Error) +} + +func Success(input any) { + output(input, pterm.Success) +} + +func Section(s string) { + style := pterm.NewStyle(pterm.BgDefault, pterm.FgLightBlue, pterm.Bold) + style.Println("\n# " + s + " \n") +} + +func DisableColor() { + pterm.DisableColor() +} + +func EnableOutput() { + pterm.EnableOutput() +} + +func DisableOutput() { + pterm.DisableOutput() +} + +func EnableDebug() { + pterm.EnableDebugMessages() +} + +func DisableDebug() { + pterm.DisableDebugMessages() +} + +func Println(s string) { + pterm.Println(s) +} + +func Hyperlink(url, text string) string { + // OSC 8 sequence to start a clickable link + linkStart := "\x1b]8;;" + + // OSC 8 sequence to end a clickable link + linkEnd := "\x1b]8;;\x1b\\" + + // ANSI escape code for underline + underlineStart := "\x1b[4m" + + // ANSI escape code to reset text formatting + resetFormat := "\x1b[0m" + + return fmt.Sprintf("%s%s%s%s%s%s%s", linkStart, url, "\x1b\\", underlineStart, text, resetFormat, linkEnd) +} diff --git a/v3/internal/version/version.go b/v3/internal/version/version.go new file mode 100644 index 000000000..fedd279af --- /dev/null +++ b/v3/internal/version/version.go @@ -0,0 +1,26 @@ +package version + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/debug" +) + +//go:embed version.txt +var versionString string + +const DevVersion = "v3.0.0-dev" + +func String() string { + if !IsDev() { + return versionString + } + return DevVersion +} + +func LatestStable() string { + return versionString +} + +func IsDev() bool { + return debug.LocalModulePath != "" +} diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt new file mode 100644 index 000000000..a63a7c224 --- /dev/null +++ b/v3/internal/version/version.txt @@ -0,0 +1 @@ +v3.0.0-alpha.17 \ No newline at end of file diff --git a/v3/pkg/application/TODO.md b/v3/pkg/application/TODO.md new file mode 100644 index 000000000..ae4700425 --- /dev/null +++ b/v3/pkg/application/TODO.md @@ -0,0 +1,10 @@ + +Features +- [ ] AssetServer +- [ ] Offline page if navigating to external URL +- [x] Application menu + +Bugs +- [ ] Resize Window +- [ ] Fullscreen/Maximise/Minimise/Restore + diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go new file mode 100644 index 000000000..cb121df6a --- /dev/null +++ b/v3/pkg/application/application.go @@ -0,0 +1,856 @@ +package application + +import ( + "context" + "embed" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "runtime" + "slices" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/internal/assetserver/bundledassets" + + "github.com/wailsapp/wails/v3/internal/signal" + + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/capabilities" +) + +//go:embed assets/* +var alphaAssets embed.FS + +var globalApplication *App + +// AlphaAssets is the default assets for the alpha application +var AlphaAssets = AssetOptions{ + Handler: BundledAssetFileServer(alphaAssets), +} + +func init() { + runtime.LockOSThread() +} + +type EventListener struct { + callback func(app *ApplicationEvent) +} + +func Get() *App { + return globalApplication +} + +func New(appOptions Options) *App { + if globalApplication != nil { + return globalApplication + } + + mergeApplicationDefaults(&appOptions) + + result := newApplication(appOptions) + globalApplication = result + fatalHandler(result.handleFatalError) + + if result.Logger == nil { + if result.isDebugMode { + result.Logger = DefaultLogger(result.options.LogLevel) + } else { + result.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + } + } + + if !appOptions.DisableDefaultSignalHandler { + result.signalHandler = signal.NewSignalHandler(result.Quit) + result.signalHandler.Logger = result.Logger + result.signalHandler.ExitMessage = func(sig os.Signal) string { + return "Quitting application..." + } + } + + result.logStartup() + result.logPlatformInfo() + + result.customEventProcessor = NewWailsEventProcessor(result.Event.dispatch) + + messageProc := NewMessageProcessor(result.Logger) + opts := &assetserver.Options{ + Handler: appOptions.Assets.Handler, + Middleware: assetserver.ChainMiddleware( + func(next http.Handler) http.Handler { + if m := appOptions.Assets.Middleware; m != nil { + return m(next) + } + return next + }, + func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + path := req.URL.Path + switch path { + case "/wails/runtime.js": + err := assetserver.ServeFile(rw, path, bundledassets.RuntimeJS) + if err != nil { + result.fatal("unable to serve runtime.js: %w", err) + } + case "/wails/runtime": + messageProc.ServeHTTP(rw, req) + case "/wails/capabilities": + err := assetserver.ServeFile(rw, path, globalApplication.capabilities.AsBytes()) + if err != nil { + result.fatal("unable to serve capabilities: %w", err) + } + case "/wails/flags": + updatedOptions := result.impl.GetFlags(appOptions) + flags, err := json.Marshal(updatedOptions) + if err != nil { + result.fatal("invalid flags provided to application: %w", err) + } + err = assetserver.ServeFile(rw, path, flags) + if err != nil { + result.fatal("unable to serve flags: %w", err) + } + default: + next.ServeHTTP(rw, req) + } + }) + }, + ), + Logger: result.Logger, + } + + if appOptions.Assets.DisableLogging { + opts.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + } + + srv, err := assetserver.NewAssetServer(opts) + if err != nil { + result.fatal("application initialisation failed: %w", err) + } + + result.assets = srv + result.assets.LogDetails() + + result.bindings = NewBindings(appOptions.MarshalError, appOptions.BindAliases) + result.options.Services = slices.Clone(appOptions.Services) + + // Process keybindings + if result.options.KeyBindings != nil { + result.keyBindings = processKeyBindingOptions(result.options.KeyBindings) + } + + if appOptions.OnShutdown != nil { + result.OnShutdown(appOptions.OnShutdown) + } + + // Initialize single instance manager if enabled + if appOptions.SingleInstance != nil { + manager, err := newSingleInstanceManager(result, appOptions.SingleInstance) + if err != nil { + if errors.Is(err, alreadyRunningError) && manager != nil { + err = manager.notifyFirstInstance() + if err != nil { + globalApplication.error("failed to notify first instance: %w", err) + } + os.Exit(appOptions.SingleInstance.ExitCode) + } + result.fatal("failed to initialize single instance manager: %w", err) + } else { + result.singleInstanceManager = manager + } + } + + return result +} + +func mergeApplicationDefaults(o *Options) { + if o.Name == "" { + o.Name = "My Wails Application" + } + if o.Description == "" { + o.Description = "An application written using Wails" + } + if o.Windows.WndClass == "" { + o.Windows.WndClass = "WailsWebviewWindow" + } +} + +type ( + platformApp interface { + run() error + destroy() + setApplicationMenu(menu *Menu) + name() string + getCurrentWindowID() uint + showAboutDialog(name string, description string, icon []byte) + setIcon(icon []byte) + on(id uint) + dispatchOnMainThread(id uint) + hide() + show() + getPrimaryScreen() (*Screen, error) + getScreens() ([]*Screen, error) + GetFlags(options Options) map[string]any + isOnMainThread() bool + isDarkMode() bool + getAccentColor() string + } + + runnable interface { + Run() + } +) + +// Messages sent from javascript get routed here +type windowMessage struct { + windowId uint + message string +} + +var windowMessageBuffer = make(chan *windowMessage, 5) + +type dragAndDropMessage struct { + windowId uint + filenames []string +} + +var windowDragAndDropBuffer = make(chan *dragAndDropMessage, 5) + +func addDragAndDropMessage(windowId uint, filenames []string) { + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: windowId, + filenames: filenames, + } +} + +var _ webview.Request = &webViewAssetRequest{} + +const webViewRequestHeaderWindowId = "x-wails-window-id" +const webViewRequestHeaderWindowName = "x-wails-window-name" + +type webViewAssetRequest struct { + webview.Request + windowId uint + windowName string +} + +var windowKeyEvents = make(chan *windowKeyEvent, 5) + +type windowKeyEvent struct { + windowId uint + acceleratorString string +} + +func (r *webViewAssetRequest) Header() (http.Header, error) { + h, err := r.Request.Header() + if err != nil { + return nil, err + } + + hh := h.Clone() + hh.Set(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(r.windowId), 10)) + return hh, nil +} + +var webviewRequests = make(chan *webViewAssetRequest, 5) + +type eventHook struct { + callback func(event *ApplicationEvent) +} + +type App struct { + ctx context.Context + cancel context.CancelFunc + options Options + applicationEventListeners map[uint][]*EventListener + applicationEventListenersLock sync.RWMutex + applicationEventHooks map[uint][]*eventHook + applicationEventHooksLock sync.RWMutex + + // Manager pattern for organized API + Window *WindowManager + ContextMenu *ContextMenuManager + KeyBinding *KeyBindingManager + Browser *BrowserManager + Env *EnvironmentManager + Dialog *DialogManager + Event *EventManager + Menu *MenuManager + Screen *ScreenManager + Clipboard *ClipboardManager + SystemTray *SystemTrayManager + + // Windows + windows map[uint]Window + windowsLock sync.RWMutex + + // System Trays + systemTrays map[uint]*SystemTray + systemTraysLock sync.Mutex + systemTrayID uint + systemTrayIDLock sync.RWMutex + + // MenuItems + menuItems map[uint]*MenuItem + menuItemsLock sync.Mutex + + // Starting and running + starting bool + running bool + runLock sync.Mutex + pendingRun []runnable + + bindings *Bindings + + // platform app + impl platformApp + + // The main application menu (private - use app.Menu.GetApplicationMenu/SetApplicationMenu) + applicationMenu *Menu + + clipboard *Clipboard + customEventProcessor *EventProcessor + Logger *slog.Logger + + contextMenus map[string]*ContextMenu + contextMenusLock sync.RWMutex + + assets *assetserver.AssetServer + startURL string + + // Hooks + windowCreatedCallbacks []func(window Window) + pid int + + // Capabilities + capabilities capabilities.Capabilities + isDebugMode bool + + // Keybindings + keyBindings map[string]func(window *WebviewWindow) + keyBindingsLock sync.RWMutex + + // Shutdown + performingShutdown bool + shutdownLock sync.Mutex + serviceShutdownLock sync.Mutex + + // Shutdown tasks are run when the application is shutting down. + // They are run in the order they are added and run on the main thread. + // The application option `OnShutdown` is run first. + shutdownTasks []func() + + // signalHandler is used to handle signals + signalHandler *signal.SignalHandler + + // Wails ApplicationEvent Listener related + wailsEventListenerLock sync.Mutex + wailsEventListeners []WailsEventListener + + // singleInstanceManager handles single instance functionality + singleInstanceManager *singleInstanceManager +} + +func (a *App) Config() Options { + return a.options +} + +// Context returns the application context that is canceled when the application shuts down. +// This context should be used for graceful shutdown of goroutines and long-running operations. +func (a *App) Context() context.Context { + return a.ctx +} + +func (a *App) handleWarning(msg string) { + if a.options.WarningHandler != nil { + a.options.WarningHandler(msg) + } else { + a.Logger.Warn(msg) + } +} + +func (a *App) handleError(err error) { + if a.options.ErrorHandler != nil { + a.options.ErrorHandler(err) + } else { + a.Logger.Error(err.Error()) + } +} + +// RegisterService appends the given service to the list of bound services. +// Registered services will be bound and initialised +// in registration order upon calling [App.Run]. +// +// RegisterService will log an error message +// and discard the given service +// if called after [App.Run]. +func (a *App) RegisterService(service Service) { + a.runLock.Lock() + defer a.runLock.Unlock() + + if a.starting || a.running { + a.error("services must be registered before running the application. Service '%s' will not be registered.", getServiceName(service)) + return + } + + a.options.Services = append(a.options.Services, service) +} + +// EmitEvent will emit an event + +func (a *App) handleFatalError(err error) { + a.handleError(&FatalError{err: err}) + os.Exit(1) +} + +func (a *App) init() { + a.ctx, a.cancel = context.WithCancel(context.Background()) + a.applicationEventHooks = make(map[uint][]*eventHook) + a.applicationEventListeners = make(map[uint][]*EventListener) + a.windows = make(map[uint]Window) + a.systemTrays = make(map[uint]*SystemTray) + a.contextMenus = make(map[string]*ContextMenu) + a.keyBindings = make(map[string]func(window *WebviewWindow)) + a.Logger = a.options.Logger + a.pid = os.Getpid() + a.wailsEventListeners = make([]WailsEventListener, 0) + + // Initialize managers + a.Window = newWindowManager(a) + a.ContextMenu = newContextMenuManager(a) + a.KeyBinding = newKeyBindingManager(a) + a.Browser = newBrowserManager(a) + a.Env = newEnvironmentManager(a) + a.Dialog = newDialogManager(a) + a.Event = newEventManager(a) + a.Menu = newMenuManager(a) + a.Screen = newScreenManager(a) + a.Clipboard = newClipboardManager(a) + a.SystemTray = newSystemTrayManager(a) +} + +func (a *App) Capabilities() capabilities.Capabilities { + return a.capabilities +} + +//func (a *App) RegisterListener(listener WailsEventListener) { +// a.wailsEventListenerLock.Lock() +// a.wailsEventListeners = append(a.wailsEventListeners, listener) +// a.wailsEventListenerLock.Unlock() +//} +// +//func (a *App) RegisterServiceHandler(prefix string, handler http.Handler) { +// a.assets.AttachServiceHandler(prefix, handler) +//} + +func (a *App) GetPID() int { + return a.pid +} + +func (a *App) info(message string, args ...any) { + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Info(message, args...) + }() + } +} + +func (a *App) debug(message string, args ...any) { + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Debug(message, args...) + }() + } +} + +func (a *App) fatal(message string, args ...any) { + err := fmt.Errorf(message, args...) + a.handleFatalError(err) +} +func (a *App) warning(message string, args ...any) { + msg := fmt.Sprintf(message, args...) + a.handleWarning(msg) +} + +func (a *App) error(message string, args ...any) { + a.handleError(fmt.Errorf(message, args...)) +} + +func (a *App) Run() error { + a.runLock.Lock() + // Prevent double invocations. + if a.starting || a.running { + a.runLock.Unlock() + return errors.New("application is running or a previous run has failed") + } + // Block further service registrations. + a.starting = true + a.runLock.Unlock() + + // Ensure application context is canceled in case of failures. + defer a.cancel() + + // Call post-create hooks + err := a.preRun() + if err != nil { + return err + } + + a.impl = newPlatformApp(a) + + // Ensure services are shut down in case of failures. + defer a.shutdownServices() + + // Ensure application context is canceled before service shutdown (duplicate calls don't hurt). + defer a.cancel() + + // Startup services before dispatching any events. + // No need to hold the lock here because a.options.Services may only change when a.running is false. + services := a.options.Services + a.options.Services = nil + for i, service := range services { + if err := a.startupService(service); err != nil { + return fmt.Errorf("error starting service '%s': %w", getServiceName(service), err) + } + // Schedule started services for shutdown. + a.options.Services = services[:i+1] + } + + go func() { + for { + event := <-applicationEvents + go a.Event.handleApplicationEvent(event) + } + }() + go func() { + for { + event := <-windowEvents + go a.handleWindowEvent(event) + } + }() + go func() { + for { + request := <-webviewRequests + go a.handleWebViewRequest(request) + } + }() + go func() { + for { + event := <-windowMessageBuffer + go a.handleWindowMessage(event) + } + }() + go func() { + for { + event := <-windowKeyEvents + go a.handleWindowKeyEvent(event) + } + }() + go func() { + for { + dragAndDropMessage := <-windowDragAndDropBuffer + go a.handleDragAndDropMessage(dragAndDropMessage) + } + }() + + go func() { + for { + menuItemID := <-menuItemClicked + go a.Menu.handleMenuItemClicked(menuItemID) + } + }() + + a.runLock.Lock() + a.running = true + a.runLock.Unlock() + + // No need to hold the lock here because + // - a.pendingRun may only change while a.running is false. + // - runnables are scheduled asynchronously anyway. + for _, pending := range a.pendingRun { + go func() { + defer handlePanic() + pending.Run() + }() + } + a.pendingRun = nil + + // set the application menu + if runtime.GOOS == "darwin" { + a.impl.setApplicationMenu(a.applicationMenu) + } + if a.options.Icon != nil { + a.impl.setIcon(a.options.Icon) + } + + return a.impl.run() +} + +func (a *App) startupService(service Service) error { + err := a.bindings.Add(service) + if err != nil { + return fmt.Errorf("cannot bind service methods: %w", err) + } + + if service.options.Route != "" { + handler, ok := service.Instance().(http.Handler) + if !ok { + handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + http.Error( + rw, + fmt.Sprintf("Service '%s' does not handle HTTP requests", getServiceName(service)), + http.StatusServiceUnavailable, + ) + }) + } + a.assets.AttachServiceHandler(service.options.Route, handler) + } + + if s, ok := service.instance.(ServiceStartup); ok { + a.debug("Starting up service:", "name", getServiceName(service)) + return s.ServiceStartup(a.ctx, service.options) + } + + return nil +} + +func (a *App) shutdownServices() { + // Acquire lock to prevent double calls (defer in Run() + OnShutdown) + a.serviceShutdownLock.Lock() + defer a.serviceShutdownLock.Unlock() + + // Ensure app context is canceled first (duplicate calls don't hurt). + a.cancel() + + for len(a.options.Services) > 0 { + last := len(a.options.Services) - 1 + service := a.options.Services[last] + a.options.Services = a.options.Services[:last] // Prevent double shutdowns + + if s, ok := service.instance.(ServiceShutdown); ok { + a.debug("Shutting down service:", "name", getServiceName(service)) + if err := s.ServiceShutdown(); err != nil { + a.error("error shutting down service '%s': %w", getServiceName(service), err) + } + } + } +} + +func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { + defer handlePanic() + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleDragAndDropMessage(event.filenames) +} + +func (a *App) handleWindowMessage(event *windowMessage) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + a.windowsLock.RUnlock() + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + // Check if the message starts with "wails:" + if strings.HasPrefix(event.message, "wails:") { + window.HandleMessage(event.message) + } else { + if a.options.RawMessageHandler != nil { + a.options.RawMessageHandler(window, event.message) + } + } +} + +func (a *App) handleWebViewRequest(request *webViewAssetRequest) { + defer handlePanic() + a.assets.ServeWebViewRequest(request) +} + +func (a *App) handleWindowEvent(event *windowEvent) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.WindowID] + a.windowsLock.RUnlock() + if !ok { + a.warning("Window #%d not found", event.WindowID) + return + } + window.HandleWindowEvent(event.EventID) +} + +// OnShutdown adds a function to be run when the application is shutting down. +func (a *App) OnShutdown(f func()) { + if f == nil { + return + } + + a.shutdownLock.Lock() + + if !a.performingShutdown { + defer a.shutdownLock.Unlock() + a.shutdownTasks = append(a.shutdownTasks, f) + return + } + + a.shutdownLock.Unlock() + InvokeAsync(f) +} + +func (a *App) cleanup() { + a.shutdownLock.Lock() + if a.performingShutdown { + a.shutdownLock.Unlock() + return + } + a.cancel() // Cancel app context before running shutdown hooks. + a.performingShutdown = true + a.shutdownLock.Unlock() + + // No need to hold the lock here because a.shutdownTasks + // may only change while a.performingShutdown is false. + for _, shutdownTask := range a.shutdownTasks { + InvokeSync(shutdownTask) + } + InvokeSync(func() { + a.shutdownServices() + a.windowsLock.RLock() + for _, window := range a.windows { + window.Close() + } + a.windows = nil + a.windowsLock.RUnlock() + a.systemTraysLock.Lock() + for _, systray := range a.systemTrays { + systray.destroy() + } + a.systemTrays = nil + a.systemTraysLock.Unlock() + + // Cleanup single instance manager + if a.singleInstanceManager != nil { + a.singleInstanceManager.cleanup() + } + + a.postQuit() + + if a.options.PostShutdown != nil { + a.options.PostShutdown() + } + }) +} + +func (a *App) Quit() { + if a.impl != nil { + InvokeSync(a.impl.destroy) + } +} + +func (a *App) SetIcon(icon []byte) { + if a.impl != nil { + a.impl.setIcon(icon) + } +} + +func InfoDialog() *MessageDialog { + return newMessageDialog(InfoDialogType) +} + +func QuestionDialog() *MessageDialog { + return newMessageDialog(QuestionDialogType) +} + +func WarningDialog() *MessageDialog { + return newMessageDialog(WarningDialogType) +} + +func ErrorDialog() *MessageDialog { + return newMessageDialog(ErrorDialogType) +} + +func OpenFileDialog() *OpenFileDialogStruct { + return newOpenFileDialog() +} + +func SaveFileDialog() *SaveFileDialogStruct { + return newSaveFileDialog() +} + +func (a *App) dispatchOnMainThread(fn func()) { + // If we are on the main thread, just call the function + if a.impl.isOnMainThread() { + fn() + return + } + + mainThreadFunctionStoreLock.Lock() + id := generateFunctionStoreID() + mainThreadFunctionStore[id] = fn + mainThreadFunctionStoreLock.Unlock() + // Call platform specific dispatch function + a.impl.dispatchOnMainThread(id) +} + +func (a *App) Hide() { + if a.impl != nil { + a.impl.hide() + } +} + +func (a *App) Show() { + if a.impl != nil { + a.impl.show() + } +} + +func (a *App) runOrDeferToAppRun(r runnable) { + a.runLock.Lock() + + if !a.running { + defer a.runLock.Unlock() // Defer unlocking for panic tolerance. + a.pendingRun = append(a.pendingRun, r) + return + } + + // Unlock immediately to prevent deadlocks. + // No TOC/TOU risk here because a.running can never switch back to false. + a.runLock.Unlock() + r.Run() +} + +func (a *App) handleWindowKeyEvent(event *windowKeyEvent) { + defer handlePanic() + // Get window from window map + a.windowsLock.RLock() + window, ok := a.windows[event.windowId] + a.windowsLock.RUnlock() + if !ok { + a.warning("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.HandleKeyEvent(event.acceleratorString) +} + +func (a *App) shouldQuit() bool { + if a.options.ShouldQuit != nil { + return a.options.ShouldQuit() + } + return true +} diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go new file mode 100644 index 000000000..cb38f7e70 --- /dev/null +++ b/v3/pkg/application/application_darwin.go @@ -0,0 +1,445 @@ +//go:build darwin + +package application + +/* + +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "application_darwin.h" +#include "application_darwin_delegate.h" +#include "webview_window_darwin.h" +#include + +extern void registerListener(unsigned int event); + +#import +#import + +static AppDelegate *appDelegate = nil; + +static void init(void) { + [NSApplication sharedApplication]; + appDelegate = [[AppDelegate alloc] init]; + [NSApp setDelegate:appDelegate]; + + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { + NSWindow* eventWindow = [event window]; + if (eventWindow == nil ) { + return event; + } + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate]; + if (windowDelegate == nil) { + return event; + } + if ([windowDelegate respondsToSelector:@selector(handleLeftMouseDown:)]) { + [windowDelegate handleLeftMouseDown:event]; + } + return event; + }]; + + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { + NSWindow* eventWindow = [event window]; + if (eventWindow == nil ) { + return event; + } + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate]; + if (windowDelegate == nil) { + return event; + } + if ([windowDelegate respondsToSelector:@selector(handleLeftMouseUp:)]) { + [windowDelegate handleLeftMouseUp:eventWindow]; + } + return event; + }]; + + NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter]; + [center addObserver:appDelegate selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; + + // Register the custom URL scheme handler + StartCustomProtocolHandler(); +} + +static bool isDarkMode(void) { + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + if (userDefaults == nil) { + return false; + } + + NSString *interfaceStyle = [userDefaults stringForKey:@"AppleInterfaceStyle"]; + if (interfaceStyle == nil) { + return false; + } + + return [interfaceStyle isEqualToString:@"Dark"]; +} + +static char* getAccentColor(void) { + @autoreleasepool { + NSColor *accentColor; + if (@available(macOS 10.14, *)) { + accentColor = [NSColor controlAccentColor]; + } else { + // Fallback to system blue for older macOS versions + accentColor = [NSColor systemBlueColor]; + } + // Convert to RGB color space + NSColor *rgbColor = [accentColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + if (rgbColor == nil) { + rgbColor = accentColor; + } + // Get RGB components + CGFloat red, green, blue, alpha; + [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; + // Convert to 0-255 range and format as rgb() string + int r = (int)(red * 255); + int g = (int)(green * 255); + int b = (int)(blue * 255); + NSString *colorString = [NSString stringWithFormat:@"rgb(%d,%d,%d)", r, g, b]; + return strdup([colorString UTF8String]); + } +} + +static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) { + // Get the NSApp delegate + AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate]; + // Set the applicationShouldTerminateAfterLastWindowClosed boolean + appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate; +} + +static void setActivationPolicy(int policy) { + [NSApp setActivationPolicy:policy]; +} + +static void activateIgnoringOtherApps() { + [NSApp activateIgnoringOtherApps:YES]; +} + +static void run(void) { + @autoreleasepool { + [NSApp run]; + [appDelegate release]; + [NSApp abortModal]; + } +} + +// destroyApp destroys the application +static void destroyApp(void) { + [NSApp terminate:nil]; +} + +// Set the application menu +static void setApplicationMenu(void *menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setMainMenu:menu]; +} + +// Get the application name +static char* getAppName(void) { + NSString *appName = [NSRunningApplication currentApplication].localizedName; + if( appName == nil ) { + appName = [[NSProcessInfo processInfo] processName]; + } + return strdup([appName UTF8String]); +} + +// get the current window ID +static unsigned int getCurrentWindowID(void) { + NSWindow *window = [NSApp keyWindow]; + // Get the window delegate + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate]; + return delegate.windowId; +} + +// Set the application icon +static void setApplicationIcon(void *icon, int length) { + // On main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [NSApp setApplicationIconImage:image]; + }); +} + +// Hide the application +static void hide(void) { + [NSApp hide:nil]; +} + +// Show the application +static void show(void) { + [NSApp unhide:nil]; +} + +static const char* serializationNSDictionary(void *dict) { + @autoreleasepool { + NSDictionary *nsDict = (__bridge NSDictionary *)dict; + + if ([NSJSONSerialization isValidJSONObject:nsDict]) { + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:nsDict options:kNilOptions error:&error]; + NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + + return strdup([result UTF8String]); + } + } + + return nil; +} + +static void startSingleInstanceListener(const char *uniqueID) { + // Convert to NSString + NSString *uid = [NSString stringWithUTF8String:uniqueID]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:appDelegate + selector:@selector(handleSecondInstanceNotification:) name:uid object:nil]; +} +*/ +import "C" +import ( + "encoding/json" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/operatingsystem" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type macosApp struct { + applicationMenu unsafe.Pointer + parent *App +} + +func (m *macosApp) isDarkMode() bool { + return bool(C.isDarkMode()) +} + +func (m *macosApp) getAccentColor() string { + accentColorC := C.getAccentColor() + defer C.free(unsafe.Pointer(accentColorC)) + return C.GoString(accentColorC) +} + +func getNativeApplication() *macosApp { + return globalApplication.impl.(*macosApp) +} + +func (m *macosApp) hide() { + C.hide() +} + +func (m *macosApp) show() { + C.show() +} + +func (m *macosApp) on(eventID uint) { + C.registerListener(C.uint(eventID)) +} + +func (m *macosApp) setIcon(icon []byte) { + C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) +} + +func (m *macosApp) name() string { + appName := C.getAppName() + defer C.free(unsafe.Pointer(appName)) + return C.GoString(appName) +} + +func (m *macosApp) getCurrentWindowID() uint { + return uint(C.getCurrentWindowID()) +} + +func (m *macosApp) setApplicationMenu(menu *Menu) { + if menu == nil { + // Create a default menu for mac + menu = DefaultApplicationMenu() + } + menu.Update() + + // Convert impl to macosMenu object + m.applicationMenu = (menu.impl).(*macosMenu).nsMenu + C.setApplicationMenu(m.applicationMenu) +} + +func (m *macosApp) run() error { + if m.parent.options.SingleInstance != nil { + cUniqueID := C.CString(m.parent.options.SingleInstance.UniqueID) + defer C.free(unsafe.Pointer(cUniqueID)) + C.startSingleInstanceListener(cUniqueID) + } + // Add a hook to the ApplicationDidFinishLaunching event + m.parent.Event.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(*ApplicationEvent) { + C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed)) + C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy)) + C.activateIgnoringOtherApps() + }) + m.setupCommonEvents() + // setup event listeners + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + C.run() + return nil +} + +func (m *macosApp) destroy() { + C.destroyApp() +} + +func (m *macosApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func newPlatformApp(app *App) *macosApp { + C.init() + return &macosApp{ + parent: app, + } +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data unsafe.Pointer) { + event := newApplicationEvent(events.ApplicationEventType(eventID)) + + if data != nil { + dataCStrJSON := C.serializationNSDictionary(data) + if dataCStrJSON != nil { + defer C.free(unsafe.Pointer(dataCStrJSON)) + + dataJSON := C.GoString(dataCStrJSON) + var result map[string]any + err := json.Unmarshal([]byte(dataJSON), &result) + + if err != nil { + panic(err) + } + + event.Context().setData(result) + } + } + + switch event.Id { + case uint(events.Mac.ApplicationDidChangeTheme): + isDark := globalApplication.Env.IsDarkMode() + event.Context().setIsDarkMode(isDark) + } + applicationEvents <- event +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +//export processMessage +func processMessage(windowID C.uint, message *C.char) { + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: C.GoString(message), + } +} + +//export processURLRequest +func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { + window, ok := globalApplication.Window.GetByID(uint(windowID)) + if !ok || window == nil { + globalApplication.debug("could not find window with id: %d", windowID) + return + } + + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(wkUrlSchemeTask), + windowId: uint(windowID), + windowName: window.Name(), + } +} + +//export processWindowKeyDownEvent +func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) { + windowKeyEvents <- &windowKeyEvent{ + windowId: uint(windowID), + acceleratorString: C.GoString(acceleratorString), + } +} + +//export processDragItems +func processDragItems(windowID C.uint, arr **C.char, length C.int) { + var filenames []string + // Convert the C array to a Go slice + goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length] + for _, str := range goSlice { + filenames = append(filenames, C.GoString(str)) + } + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: uint(windowID), + filenames: filenames, + } +} + +//export processMenuItemClick +func processMenuItemClick(menuID C.uint) { + menuItemClicked <- uint(menuID) +} + +//export shouldQuitApplication +func shouldQuitApplication() C.bool { + // TODO: This should be configurable + return C.bool(globalApplication.shouldQuit()) +} + +//export cleanup +func cleanup() { + globalApplication.cleanup() +} + +func (a *App) logPlatformInfo() { + info, err := operatingsystem.Info() + if err != nil { + a.error("error getting OS info: %w", err) + return + } + + a.info("Platform Info:", info.AsLogSlice()...) + +} + +func (a *App) platformEnvironment() map[string]any { + return map[string]any{} +} + +func fatalHandler(errFunc func(error)) { + return +} + +//export HandleOpenFile +func HandleOpenFile(filePath *C.char) { + goFilepath := C.GoString(filePath) + // Create new application event context + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(goFilepath) + // EmitEvent application started event + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } +} + +//export HandleCustomProtocol +func HandleCustomProtocol(urlCString *C.char) { + urlString := C.GoString(urlCString) + eventContext := newApplicationEventContext() + eventContext.setURL(urlString) + + // Emit the standard event with the URL string as data + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } +} diff --git a/v3/pkg/application/application_darwin.h b/v3/pkg/application/application_darwin.h new file mode 100644 index 000000000..e67384397 --- /dev/null +++ b/v3/pkg/application/application_darwin.h @@ -0,0 +1,11 @@ +//go:build darwin + +#ifndef application_h +#define application_h + +static void init(void); +static void run(void); +static void setActivationPolicy(int policy); +static char *getAppName(void); + +#endif \ No newline at end of file diff --git a/v3/pkg/application/application_darwin_delegate.h b/v3/pkg/application/application_darwin_delegate.h new file mode 100644 index 000000000..77c30898b --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.h @@ -0,0 +1,25 @@ +//go:build darwin + +#ifndef appdelegate_h +#define appdelegate_h + +#import + +@interface AppDelegate : NSResponder +@property bool shouldTerminateWhenLastWindowClosed; +@property bool shuttingDown; +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; +@end + +extern void HandleOpenFile(char *); + +// Declarations for Apple Event based custom URL handling +extern void HandleCustomProtocol(char*); + +@interface CustomProtocolSchemeHandler : NSObject ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end + +void StartCustomProtocolHandler(void); + +#endif /* appdelegate_h */ diff --git a/v3/pkg/application/application_darwin_delegate.m b/v3/pkg/application/application_darwin_delegate.m new file mode 100644 index 000000000..87504e62b --- /dev/null +++ b/v3/pkg/application/application_darwin_delegate.m @@ -0,0 +1,198 @@ +//go:build darwin +#import "application_darwin_delegate.h" +#import "../events/events_darwin.h" +#import // For Apple Event constants +extern bool hasListeners(unsigned int); +extern bool shouldQuitApplication(); +extern void cleanup(); +extern void handleSecondInstanceData(char * message); +@implementation AppDelegate +- (void)dealloc +{ + [super dealloc]; +} +-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename + { + const char* utf8FileName = filename.UTF8String; + HandleOpenFile((char*)utf8FileName); + return YES; + } +// Create the applicationShouldTerminateAfterLastWindowClosed: method +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return self.shouldTerminateWhenLastWindowClosed; +} +- (void)themeChanged:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeTheme) ) { + processApplicationEvent(EventApplicationDidChangeTheme, NULL); + } +} +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + if( ! shouldQuitApplication() ) { + return NSTerminateCancel; + } + if( !self.shuttingDown ) { + self.shuttingDown = true; + cleanup(); + } + return NSTerminateNow; +} +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + return YES; +} +- (BOOL)applicationShouldHandleReopen:(NSNotification *)notification + hasVisibleWindows:(BOOL)flag { // Changed from NSApplication to NSNotification + if( hasListeners(EventApplicationShouldHandleReopen) ) { + processApplicationEvent(EventApplicationShouldHandleReopen, @{@"hasVisibleWindows": @(flag)}); + } + + return TRUE; +} +- (void)handleSecondInstanceNotification:(NSNotification *)note; +{ + if (note.userInfo[@"message"] != nil) { + NSString *message = note.userInfo[@"message"]; + const char* utf8Message = message.UTF8String; + handleSecondInstanceData((char*)utf8Message); + } +} +// GENERATED EVENTS START +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidBecomeActive) ) { + processApplicationEvent(EventApplicationDidBecomeActive, NULL); + } +} + +- (void)applicationDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeBackingProperties) ) { + processApplicationEvent(EventApplicationDidChangeBackingProperties, NULL); + } +} + +- (void)applicationDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeEffectiveAppearance) ) { + processApplicationEvent(EventApplicationDidChangeEffectiveAppearance, NULL); + } +} + +- (void)applicationDidChangeIcon:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeIcon) ) { + processApplicationEvent(EventApplicationDidChangeIcon, NULL); + } +} + +- (void)applicationDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeOcclusionState) ) { + processApplicationEvent(EventApplicationDidChangeOcclusionState, NULL); + } +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeScreenParameters) ) { + processApplicationEvent(EventApplicationDidChangeScreenParameters, NULL); + } +} + +- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarFrame) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarFrame, NULL); + } +} + +- (void)applicationDidChangeStatusBarOrientation:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarOrientation) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarOrientation, NULL); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationDidFinishLaunching) ) { + processApplicationEvent(EventApplicationDidFinishLaunching, NULL); + } +} + +- (void)applicationDidHide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidHide) ) { + processApplicationEvent(EventApplicationDidHide, NULL); + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidResignActive) ) { + processApplicationEvent(EventApplicationDidResignActive, NULL); + } +} + +- (void)applicationDidUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUnhide) ) { + processApplicationEvent(EventApplicationDidUnhide, NULL); + } +} + +- (void)applicationDidUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUpdate) ) { + processApplicationEvent(EventApplicationDidUpdate, NULL); + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillBecomeActive) ) { + processApplicationEvent(EventApplicationWillBecomeActive, NULL); + } +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationWillFinishLaunching) ) { + processApplicationEvent(EventApplicationWillFinishLaunching, NULL); + } +} + +- (void)applicationWillHide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillHide) ) { + processApplicationEvent(EventApplicationWillHide, NULL); + } +} + +- (void)applicationWillResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillResignActive) ) { + processApplicationEvent(EventApplicationWillResignActive, NULL); + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillTerminate) ) { + processApplicationEvent(EventApplicationWillTerminate, NULL); + } +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUnhide) ) { + processApplicationEvent(EventApplicationWillUnhide, NULL); + } +} + +- (void)applicationWillUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUpdate) ) { + processApplicationEvent(EventApplicationWillUpdate, NULL); + } +} + +// GENERATED EVENTS END +@end +// Implementation for Apple Event based custom URL handling +@implementation CustomProtocolSchemeHandler ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + if (urlStr) { + HandleCustomProtocol((char*)[urlStr UTF8String]); + } +} +@end +void StartCustomProtocolHandler(void) { + NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:[CustomProtocolSchemeHandler class] + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID: kAEGetURL]; +} diff --git a/v3/pkg/application/application_debug.go b/v3/pkg/application/application_debug.go new file mode 100644 index 000000000..bc850c3e8 --- /dev/null +++ b/v3/pkg/application/application_debug.go @@ -0,0 +1,71 @@ +//go:build !production + +package application + +import ( + "github.com/go-git/go-git/v5" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/version" + "path/filepath" + "runtime/debug" +) + +// BuildSettings contains the build settings for the application +var BuildSettings map[string]string + +// BuildInfo contains the build info for the application +var BuildInfo *debug.BuildInfo + +func init() { + var ok bool + BuildInfo, ok = debug.ReadBuildInfo() + if !ok { + return + } + BuildSettings = lo.Associate(BuildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) +} + +// We use this to patch the application to production mode. +func newApplication(options Options) *App { + result := &App{ + isDebugMode: true, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() { + var args []any + + // BuildInfo is nil when build with garble + if BuildInfo == nil { + return + } + + wailsPackage, _ := lo.Find(BuildInfo.Deps, func(dep *debug.Module) bool { + return dep.Path == "github.com/wailsapp/wails/v3" + }) + + wailsVersion := version.String() + if wailsPackage != nil && wailsPackage.Replace != nil { + wailsVersion = "(local) => " + filepath.ToSlash(wailsPackage.Replace.Path) + // Get the latest commit hash + repo, err := git.PlainOpen(filepath.Join(wailsPackage.Replace.Path, "..")) + if err == nil { + head, err := repo.Head() + if err == nil { + wailsVersion += " (" + head.Hash().String()[:8] + ")" + } + } + } + args = append(args, "Wails", wailsVersion) + args = append(args, "Compiler", BuildInfo.GoVersion) + for key, value := range BuildSettings { + args = append(args, key, value) + } + + a.info("Build Info:", args...) +} diff --git a/v3/pkg/application/application_dev.go b/v3/pkg/application/application_dev.go new file mode 100644 index 000000000..e12033e33 --- /dev/null +++ b/v3/pkg/application/application_dev.go @@ -0,0 +1,51 @@ +//go:build !production + +package application + +import ( + "net/http" + "time" + + "github.com/wailsapp/wails/v3/internal/assetserver" +) + +var devMode = false + +func (a *App) preRun() error { + // Check for frontend server url + frontendURL := assetserver.GetDevServerURL() + if frontendURL != "" { + devMode = true + // We want to check if the frontend server is running by trying to http get the url + // and if it is not, we wait 500ms and try again for a maximum of 10 times. If it is + // still not available, we return an error. + // This is to allow the frontend server to start up before the backend server. + client := http.Client{} + a.Logger.Info("Waiting for frontend dev server to start...", "url", frontendURL) + for i := 0; i < 10; i++ { + _, err := client.Get(frontendURL) + if err == nil { + a.Logger.Info("Connected to frontend dev server!") + return nil + } + // Wait 500ms + time.Sleep(500 * time.Millisecond) + if i%2 == 0 { + a.Logger.Info("Retrying...") + } + } + a.fatal("unable to connect to frontend server. Please check it is running - FRONTEND_DEVSERVER_URL='%s'", frontendURL) + } + return nil +} + +func (a *App) postQuit() { + if devMode { + a.Logger.Info("The application has terminated, but the watcher is still running.") + a.Logger.Info("To terminate the watcher, press CTRL+C") + } +} + +func (a *App) enableDevTools() { + +} diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go new file mode 100644 index 000000000..b92c2c6aa --- /dev/null +++ b/v3/pkg/application/application_linux.go @@ -0,0 +1,309 @@ +//go:build linux + +package application + +/* + #include "gtk/gtk.h" + #include "webkit2/webkit2.h" + static guint get_compiled_gtk_major_version() { return GTK_MAJOR_VERSION; } + static guint get_compiled_gtk_minor_version() { return GTK_MINOR_VERSION; } + static guint get_compiled_gtk_micro_version() { return GTK_MICRO_VERSION; } + static guint get_compiled_webkit_major_version() { return WEBKIT_MAJOR_VERSION; } + static guint get_compiled_webkit_minor_version() { return WEBKIT_MINOR_VERSION; } + static guint get_compiled_webkit_micro_version() { return WEBKIT_MICRO_VERSION; } +*/ +import "C" +import ( + "fmt" + "os" + "slices" + "strings" + "sync" + + "path/filepath" + + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func init() { + // FIXME: This should be handled appropriately in the individual files most likely. + // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings + if os.Getenv("GDK_BACKEND") == "" && + (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") { + _ = os.Setenv("GDK_BACKEND", "x11") + } +} + +type linuxApp struct { + application pointer + parent *App + + startupActions []func() + + // Native -> uint + windowMap map[windowPointer]uint + windowMapLock sync.Mutex + + theme string + + icon pointer +} + +func (a *linuxApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + return options.Flags +} + +func getNativeApplication() *linuxApp { + return globalApplication.impl.(*linuxApp) +} + +func (a *linuxApp) hide() { + a.hideAllWindows() +} + +func (a *linuxApp) show() { + a.showAllWindows() +} + +func (a *linuxApp) on(eventID uint) { + // TODO: Test register/unregister events + //C.registerApplicationEvent(l.application, C.uint(eventID)) +} + +func (a *linuxApp) name() string { + return appName() +} + +type rnr struct { + f func() +} + +func (r rnr) run() { + r.f() +} + +func (a *linuxApp) setApplicationMenu(menu *Menu) { + // FIXME: How do we avoid putting a menu? + if menu == nil { + // Create a default menu + menu = DefaultApplicationMenu() + globalApplication.applicationMenu = menu + } +} + +func (a *linuxApp) run() error { + + if len(os.Args) == 2 { // Case: program + 1 argument + arg1 := os.Args[1] + // Check if the argument is likely a URL from a custom protocol invocation + if strings.Contains(arg1, "://") { + a.parent.info("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + eventContext := newApplicationEventContext() + eventContext.setURL(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } + } else { + // Check if the argument matches any file associations + if a.parent.options.FileAssociations != nil { + ext := filepath.Ext(arg1) + if slices.Contains(a.parent.options.FileAssociations, ext) { + a.parent.info("File opened via file association", "file", arg1, "extension", ext) + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } + return nil + } + } + a.parent.info("Application launched with single argument (not a URL), potential file open?", "arg", arg1) + } + } else if len(os.Args) > 2 { + // Log if multiple arguments are passed + a.parent.info("Application launched with multiple arguments", "args", os.Args[1:]) + } + + a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) { + // TODO: What should happen here? + }) + a.setupCommonEvents() + a.monitorThemeChanges() + return appRun(a.application) +} + +func (a *linuxApp) unregisterWindow(w windowPointer) { + a.windowMapLock.Lock() + delete(a.windowMap, w) + a.windowMapLock.Unlock() + + // If this was the last window... + if len(a.windowMap) == 0 && !a.parent.options.Linux.DisableQuitOnLastWindowClosed { + a.destroy() + } +} + +func (a *linuxApp) destroy() { + if !globalApplication.shouldQuit() { + return + } + globalApplication.cleanup() + appDestroy(a.application) +} + +func (a *linuxApp) isOnMainThread() bool { + return isOnMainThread() +} + +// register our window to our parent mapping +func (a *linuxApp) registerWindow(window pointer, id uint) { + a.windowMapLock.Lock() + a.windowMap[windowPointer(window)] = id + a.windowMapLock.Unlock() +} + +func (a *linuxApp) isDarkMode() bool { + return strings.Contains(a.theme, "dark") +} + +func (a *linuxApp) getAccentColor() string { + // Linux doesn't have a unified system accent color API + // Return a default blue color + return "rgb(0,122,255)" +} + +func (a *linuxApp) monitorThemeChanges() { + go func() { + defer handlePanic() + conn, err := dbus.ConnectSessionBus() + if err != nil { + a.parent.info( + "[WARNING] Failed to connect to session bus; monitoring for theme changes will not function:", + err, + ) + return + } + defer conn.Close() + + if err = conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"), + ); err != nil { + panic(err) + } + + c := make(chan *dbus.Signal, 10) + conn.Signal(c) + + getTheme := func(body []interface{}) (string, bool) { + if len(body) < 2 { + return "", false + } + if entry, ok := body[0].(string); !ok || entry != "org.gnome.desktop.interface" { + return "", false + } + if entry, ok := body[1].(string); ok && entry == "color-scheme" { + return body[2].(dbus.Variant).Value().(string), true + } + return "", false + } + + for v := range c { + theme, ok := getTheme(v.Body) + if !ok { + continue + } + + if theme != a.theme { + a.theme = theme + event := newApplicationEvent(events.Linux.SystemThemeChanged) + event.Context().setIsDarkMode(a.isDarkMode()) + applicationEvents <- event + } + + } + }() +} + +func newPlatformApp(parent *App) *linuxApp { + + name := strings.ToLower(strings.Replace(parent.options.Name, " ", "", -1)) + if name == "" { + name = "undefined" + } + app := &linuxApp{ + parent: parent, + application: appNew(name), + windowMap: map[windowPointer]uint{}, + } + + if parent.options.Linux.ProgramName != "" { + setProgramName(parent.options.Linux.ProgramName) + } + + return app +} + +// logPlatformInfo logs the platform information to the console +func (a *App) logPlatformInfo() { + info, err := operatingsystem.Info() + if err != nil { + a.error("error getting OS info: %w", err) + return + } + + wkVersion := operatingsystem.GetWebkitVersion() + platformInfo := info.AsLogSlice() + platformInfo = append(platformInfo, "Webkit2Gtk", wkVersion) + + a.info("Platform Info:", platformInfo...) +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &windowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +func buildVersionString(major, minor, micro C.uint) string { + return fmt.Sprintf("%d.%d.%d", uint(major), uint(minor), uint(micro)) +} + +func (a *App) platformEnvironment() map[string]any { + result := map[string]any{} + result["gtk3-compiled"] = buildVersionString( + C.get_compiled_gtk_major_version(), + C.get_compiled_gtk_minor_version(), + C.get_compiled_gtk_micro_version(), + ) + result["gtk3-runtime"] = buildVersionString( + C.gtk_get_major_version(), + C.gtk_get_minor_version(), + C.gtk_get_micro_version(), + ) + + result["webkit2gtk-compiled"] = buildVersionString( + C.get_compiled_webkit_major_version(), + C.get_compiled_webkit_minor_version(), + C.get_compiled_webkit_micro_version(), + ) + result["webkit2gtk-runtime"] = buildVersionString( + C.webkit_get_major_version(), + C.webkit_get_minor_version(), + C.webkit_get_micro_version(), + ) + return result +} + +func fatalHandler(errFunc func(error)) { + // Stub for windows function + return +} diff --git a/v3/pkg/application/application_options.go b/v3/pkg/application/application_options.go new file mode 100644 index 000000000..76d77457d --- /dev/null +++ b/v3/pkg/application/application_options.go @@ -0,0 +1,225 @@ +package application + +import ( + "io/fs" + "log/slog" + "net/http" + + "github.com/wailsapp/wails/v3/internal/assetserver" +) + +// Options contains the options for the application +type Options struct { + // Name is the name of the application (used in the default about box) + Name string + + // Description is the description of the application (used in the default about box) + Description string + + // Icon is the icon of the application (used in the default about box) + Icon []byte + + // Mac is the Mac specific configuration for Mac builds + Mac MacOptions + + // Windows is the Windows specific configuration for Windows builds + Windows WindowsOptions + + // Linux is the Linux specific configuration for Linux builds + Linux LinuxOptions + + // Services allows you to bind Go methods to the frontend. + Services []Service + + // MarshalError will be called if non-nil + // to marshal to JSON the error values returned by service methods. + // + // MarshalError is not allowed to fail, + // but it may return a nil slice to fall back + // to the default error handling mechanism. + // + // If the returned slice is not nil, it must contain valid JSON. + MarshalError func(error) []byte + + // BindAliases allows you to specify alias IDs for your bound methods. + // Example: `BindAliases: map[uint32]uint32{1: 1411160069}` states that alias ID 1 maps to the Go method with ID 1411160069. + BindAliases map[uint32]uint32 + + // Logger is a slog.Logger instance used for logging Wails system messages (not application messages). + // If not defined, a default logger is used. + Logger *slog.Logger + + // LogLevel defines the log level of the Wails system logger. + LogLevel slog.Level + + // Assets are the application assets to be used. + Assets AssetOptions + + // Flags are key value pairs that are available to the frontend. + // This is also used by Wails to provide information to the frontend. + Flags map[string]any + + // PanicHandler is called when a panic occurs + PanicHandler func(*PanicDetails) + + // DisableDefaultSignalHandler disables the default signal handler + DisableDefaultSignalHandler bool + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) + + // OnShutdown is called when the application is about to terminate. + // This is useful for cleanup tasks. + // The shutdown process blocks until this function returns. + OnShutdown func() + + // PostShutdown is called after the application + // has finished shutting down, just before process termination. + // This is useful for testing and logging purposes + // on platforms where the Run() method does not return. + // When PostShutdown is called, the application instance is not usable anymore. + // The shutdown process blocks until this function returns. + PostShutdown func() + + // ShouldQuit is a function that is called when the user tries to quit the application. + // If the function returns true, the application will quit. + // If the function returns false, the application will not quit. + ShouldQuit func() bool + + // RawMessageHandler is called when the frontend sends a raw message. + // This is useful for implementing custom frontend-to-backend communication. + RawMessageHandler func(window Window, message string) + + // WarningHandler is called when a warning occurs + WarningHandler func(string) + + // ErrorHandler is called when an error occurs + ErrorHandler func(err error) + + // File extensions associated with the application + // Example: [".txt", ".md"] + // The '.' is required + FileAssociations []string + + // SingleInstance options for single instance functionality + SingleInstance *SingleInstanceOptions +} + +// AssetOptions defines the configuration of the AssetServer. +type AssetOptions struct { + // Handler which serves all the content to the WebView. + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // This middleware injects itself before any of Wails internal middlewares. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware + + // DisableLogging disables logging of the AssetServer. By default, the AssetServer logs every request. + DisableLogging bool +} + +// Middleware defines HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} + +// AssetFileServerFS returns a http handler which serves the assets from the fs.FS. +// If an external devserver has been provided 'FRONTEND_DEVSERVER_URL' the files are being served +// from the external server, ignoring the `assets`. +func AssetFileServerFS(assets fs.FS) http.Handler { + return assetserver.NewAssetFileServer(assets) +} + +// BundledAssetFileServer returns a http handler which serves the assets from the fs.FS. +// If an external devserver has been provided 'FRONTEND_DEVSERVER_URL' the files are being served +// from the external server, ignoring the `assets`. +// It also serves the compiled runtime.js file at `/wails/runtime.js`. +// It will provide the production runtime.js file from the embedded assets if the `production` tag is used. +func BundledAssetFileServer(assets fs.FS) http.Handler { + return assetserver.NewBundledAssetFileServer(assets) +} + +/******** Mac Options ********/ + +// ActivationPolicy is the activation policy for the application. +type ActivationPolicy int + +const ( + // ActivationPolicyRegular is used for applications that have a user interface, + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is used for applications that do not have a main window, + // such as system tray applications or background applications. + ActivationPolicyAccessory + ActivationPolicyProhibited +) + +// MacOptions contains options for macOS applications. +type MacOptions struct { + // ActivationPolicy is the activation policy for the application. Defaults to + // applicationActivationPolicyRegular. + ActivationPolicy ActivationPolicy + // If set to true, the application will terminate when the last window is closed. + ApplicationShouldTerminateAfterLastWindowClosed bool +} + +/****** Windows Options *******/ + +// WindowsOptions contains options for Windows applications. +type WindowsOptions struct { + + // Window class name + // Default: WailsWebviewWindow + WndClass string + + // WndProcInterceptor is a function that will be called for every message sent in the application. + // Use this to hook into the main message loop. This is useful for handling custom window messages. + // If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop. + // If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop. + WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool) + + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // Path where the WebView2 stores the user data. If empty %APPDATA%\[BinaryName.exe] will be used. + // If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code. + WebviewUserDataPath string + + // Path to the directory with WebView2 executables. If empty WebView2 installed in the system will be used. + WebviewBrowserPath string +} + +/********* Linux Options *********/ + +// LinuxOptions contains options for Linux applications. +type LinuxOptions struct { + // DisableQuitOnLastWindowClosed disables the auto quit of the application if the last window has been closed. + DisableQuitOnLastWindowClosed bool + + // ProgramName is used to set the program's name for the window manager via GTK's g_set_prgname(). + //This name should not be localized. [see the docs] + // + //When a .desktop file is created this value helps with window grouping and desktop icons when the .desktop file's Name + //property differs form the executable's filename. + // + //[see the docs]: https://docs.gtk.org/glib/func.set_prgname.html + ProgramName string +} diff --git a/v3/pkg/application/application_production.go b/v3/pkg/application/application_production.go new file mode 100644 index 000000000..75f86b44d --- /dev/null +++ b/v3/pkg/application/application_production.go @@ -0,0 +1,18 @@ +//go:build production + +package application + +func newApplication(options Options) *App { + result := &App{ + isDebugMode: false, + options: options, + } + result.init() + return result +} + +func (a *App) logStartup() {} + +func (a *App) preRun() error { return nil } + +func (a *App) postQuit() error { return nil } diff --git a/v3/pkg/application/application_windows.go b/v3/pkg/application/application_windows.go new file mode 100644 index 000000000..f72abf963 --- /dev/null +++ b/v3/pkg/application/application_windows.go @@ -0,0 +1,434 @@ +//go:build windows + +package application + +import ( + "errors" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v3/internal/operatingsystem" + + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var ( + wmTaskbarCreated = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("TaskbarCreated")) +) + +type windowsApp struct { + parent *App + + windowClass w32.WNDCLASSEX + instance w32.HINSTANCE + + windowMap map[w32.HWND]*windowsWebviewWindow + windowMapLock sync.RWMutex + + systrayMap map[w32.HMENU]*windowsSystemTray + systrayMapLock sync.RWMutex + + mainThreadID w32.HANDLE + mainThreadWindowHWND w32.HWND + + // Windows hidden by application.Hide() + hiddenWindows []*windowsWebviewWindow + focusedWindow w32.HWND + + // system theme + isCurrentlyDarkMode bool + currentWindowID uint + + // Restart taskbar flag + restartingTaskbar atomic.Bool +} + +func (m *windowsApp) isDarkMode() bool { + return w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) getAccentColor() string { + accentColor, err := w32.GetAccentColor() + if err != nil { + m.parent.error("failed to get accent color: %w", err) + return "rgb(0,122,255)" + } + + return accentColor +} + +func (m *windowsApp) isOnMainThread() bool { + return m.mainThreadID == w32.GetCurrentThreadId() +} + +func (m *windowsApp) GetFlags(options Options) map[string]any { + if options.Flags == nil { + options.Flags = make(map[string]any) + } + options.Flags["system"] = map[string]any{ + "resizeHandleWidth": w32.GetSystemMetrics(w32.SM_CXSIZEFRAME), + "resizeHandleHeight": w32.GetSystemMetrics(w32.SM_CYSIZEFRAME), + } + return options.Flags +} + +func (m *windowsApp) getWindowForHWND(hwnd w32.HWND) *windowsWebviewWindow { + m.windowMapLock.RLock() + defer m.windowMapLock.RUnlock() + return m.windowMap[hwnd] +} + +func getNativeApplication() *windowsApp { + return globalApplication.impl.(*windowsApp) +} + +func (m *windowsApp) hide() { + // Get the current focussed window + m.focusedWindow = w32.GetForegroundWindow() + + // Iterate over all windows and hide them if they aren't already hidden + for _, window := range m.windowMap { + if window.isVisible() { + // Add to hidden windows + m.hiddenWindows = append(m.hiddenWindows, window) + window.hide() + } + } + // Switch focus to the next application + hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT) + w32.SetForegroundWindow(hwndNext) +} + +func (m *windowsApp) show() { + // Iterate over all windows and show them if they were previously hidden + for _, window := range m.hiddenWindows { + window.show() + } + // Show the foreground window + w32.SetForegroundWindow(m.focusedWindow) +} + +func (m *windowsApp) on(_ uint) { +} + +func (m *windowsApp) setIcon(_ []byte) { +} + +func (m *windowsApp) name() string { + //appName := C.getAppName() + //defer C.free(unsafe.Pointer(appName)) + //return C.GoString(appName) + return "" +} + +func (m *windowsApp) getCurrentWindowID() uint { + return m.currentWindowID +} + +func (m *windowsApp) setApplicationMenu(menu *Menu) { + if menu == nil { + // Create a default menu for windows + menu = DefaultApplicationMenu() + } + menu.Update() + + m.parent.applicationMenu = menu +} + +func (m *windowsApp) run() error { + m.setupCommonEvents() + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + // EmitEvent application started event + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Windows.ApplicationStarted), + ctx: blankApplicationEventContext, + } + + if len(os.Args) == 2 { // Case: program + 1 argument + arg1 := os.Args[1] + // Check if the argument is likely a URL from a custom protocol invocation + if strings.Contains(arg1, "://") { + m.parent.info("Application launched with argument, potentially a URL from custom protocol", "url", arg1) + eventContext := newApplicationEventContext() + eventContext.setURL(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationLaunchedWithUrl), + ctx: eventContext, + } + } else { + // If not a URL-like string, check for file association + if m.parent.options.FileAssociations != nil { + ext := filepath.Ext(arg1) + if slices.Contains(m.parent.options.FileAssociations, ext) { + m.parent.info("Application launched with file via file association", "file", arg1) + eventContext := newApplicationEventContext() + eventContext.setOpenedWithFile(arg1) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Common.ApplicationOpenedWithFile), + ctx: eventContext, + } + } + } + } + } else if len(os.Args) > 2 { + // Log if multiple arguments are passed, though typical protocol/file launch is a single arg. + m.parent.info("Application launched with multiple arguments", "args", os.Args[1:]) + } + + _ = m.runMainLoop() + + return nil +} + +func (m *windowsApp) destroy() { + if !globalApplication.shouldQuit() { + return + } + globalApplication.cleanup() + // destroy the main thread window + w32.DestroyWindow(m.mainThreadWindowHWND) + // Post a quit message to the main thread + w32.PostQuitMessage(0) +} + +func (m *windowsApp) init() { + // Register the window class + + icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION) + + m.windowClass.Size = uint32(unsafe.Sizeof(m.windowClass)) + m.windowClass.Style = w32.CS_HREDRAW | w32.CS_VREDRAW + m.windowClass.WndProc = syscall.NewCallback(m.wndProc) + m.windowClass.Instance = m.instance + m.windowClass.Background = w32.COLOR_BTNFACE + 1 + m.windowClass.Icon = icon + m.windowClass.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW) + m.windowClass.ClassName = w32.MustStringToUTF16Ptr(m.parent.options.Windows.WndClass) + m.windowClass.MenuName = nil + m.windowClass.IconSm = icon + + if ret := w32.RegisterClassEx(&m.windowClass); ret == 0 { + panic(syscall.GetLastError()) + } + m.isCurrentlyDarkMode = w32.IsCurrentlyDarkMode() +} + +func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr { + + // Handle the invoke callback + if msg == wmInvokeCallback { + m.invokeCallback(wParam, lParam) + return 0 + } + + // If the WndProcInterceptor is set in options, pass the message on + if m.parent.options.Windows.WndProcInterceptor != nil { + returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam) + if shouldReturn { + return returnValue + } + } + + // Handle the main thread window + // Quit the application if requested + // Reprocess and cache screens when display settings change + if hwnd == m.mainThreadWindowHWND { + if msg == w32.WM_ENDSESSION || msg == w32.WM_DESTROY || msg == w32.WM_CLOSE { + globalApplication.Quit() + } + if msg == w32.WM_DISPLAYCHANGE || (msg == w32.WM_SETTINGCHANGE && wParam == w32.SPI_SETWORKAREA) { + err := m.processAndCacheScreens() + if err != nil { + m.parent.handleError(err) + } + } + } + + switch msg { + case wmTaskbarCreated: + if m.restartingTaskbar.Load() { + break + } + m.restartingTaskbar.Store(true) + m.reshowSystrays() + go func() { + // 1 second debounce + time.Sleep(1000) + m.restartingTaskbar.Store(false) + }() + case w32.WM_SETTINGCHANGE: + settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam))) + if settingChanged == "ImmersiveColorSet" { + isDarkMode := w32.IsCurrentlyDarkMode() + if isDarkMode != m.isCurrentlyDarkMode { + eventContext := newApplicationEventContext() + eventContext.setIsDarkMode(isDarkMode) + applicationEvents <- &ApplicationEvent{ + Id: uint(events.Windows.SystemThemeChanged), + ctx: eventContext, + } + m.isCurrentlyDarkMode = isDarkMode + } + } + return 0 + case w32.WM_POWERBROADCAST: + switch wParam { + case w32.PBT_APMPOWERSTATUSCHANGE: + applicationEvents <- newApplicationEvent(events.Windows.APMPowerStatusChange) + case w32.PBT_APMSUSPEND: + applicationEvents <- newApplicationEvent(events.Windows.APMSuspend) + case w32.PBT_APMRESUMEAUTOMATIC: + applicationEvents <- newApplicationEvent(events.Windows.APMResumeAutomatic) + case w32.PBT_APMRESUMESUSPEND: + applicationEvents <- newApplicationEvent(events.Windows.APMResumeSuspend) + case w32.PBT_POWERSETTINGCHANGE: + applicationEvents <- newApplicationEvent(events.Windows.APMPowerSettingChange) + } + return 0 + } + + if window, ok := m.windowMap[hwnd]; ok { + return window.WndProc(msg, wParam, lParam) + } + + m.systrayMapLock.Lock() + systray, ok := m.systrayMap[hwnd] + m.systrayMapLock.Unlock() + if ok { + return systray.wndProc(msg, wParam, lParam) + } + + // Dispatch the message to the appropriate window + + return w32.DefWindowProc(hwnd, msg, wParam, lParam) +} + +func (m *windowsApp) registerWindow(result *windowsWebviewWindow) { + m.windowMapLock.Lock() + m.windowMap[result.hwnd] = result + m.windowMapLock.Unlock() +} + +func (m *windowsApp) registerSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + m.systrayMap[result.hwnd] = result +} + +func (m *windowsApp) unregisterSystemTray(result *windowsSystemTray) { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + delete(m.systrayMap, result.hwnd) +} + +func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) { + m.windowMapLock.Lock() + delete(m.windowMap, w.hwnd) + m.windowMapLock.Unlock() + + // If this was the last window... + if len(m.windowMap) == 0 && !m.parent.options.Windows.DisableQuitOnLastWindowClosed { + w32.PostQuitMessage(0) + } +} + +func (m *windowsApp) reshowSystrays() { + m.systrayMapLock.Lock() + defer m.systrayMapLock.Unlock() + for _, systray := range m.systrayMap { + systray.reshow() + } +} + +func setupDPIAwareness() error { + // https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process + // https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows + + if w32.HasSetProcessDpiAwarenessContextFunc() { + // This is most recent version with the best results + // supported beginning with Windows 10, version 1703 + return w32.SetProcessDpiAwarenessContext(w32.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + } + + if w32.HasSetProcessDpiAwarenessFunc() { + // Supported beginning with Windows 8.1 + return w32.SetProcessDpiAwareness(w32.PROCESS_PER_MONITOR_DPI_AWARE) + } + + if w32.HasSetProcessDPIAwareFunc() { + // If none of the above is supported, fallback to SetProcessDPIAware + // which is supported beginning with Windows Vista + return w32.SetProcessDPIAware() + } + + return errors.New("no DPI awareness method supported") +} + +func newPlatformApp(app *App) *windowsApp { + + err := setupDPIAwareness() + if err != nil { + app.handleError(err) + } + + result := &windowsApp{ + parent: app, + instance: w32.GetModuleHandle(""), + windowMap: make(map[w32.HWND]*windowsWebviewWindow), + systrayMap: make(map[w32.HWND]*windowsSystemTray), + } + + err = result.processAndCacheScreens() + if err != nil { + app.handleFatalError(err) + } + + result.init() + result.initMainLoop() + + return result +} + +func (a *App) logPlatformInfo() { + var args []any + args = append(args, "Go-WebView2Loader", webviewloader.UsingGoWebview2Loader) + webviewVersion, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString( + a.options.Windows.WebviewBrowserPath, + ) + if err != nil { + args = append(args, "WebView2", "Error: "+err.Error()) + } else { + args = append(args, "WebView2", webviewVersion) + } + + osInfo, _ := operatingsystem.Info() + args = append(args, osInfo.AsLogSlice()...) + + a.info("Platform Info:", args...) +} + +func (a *App) platformEnvironment() map[string]any { + result := map[string]any{} + webviewVersion, _ := webviewloader.GetAvailableCoreWebView2BrowserVersionString( + a.options.Windows.WebviewBrowserPath, + ) + result["Go-WebView2Loader"] = webviewloader.UsingGoWebview2Loader + result["WebView2"] = webviewVersion + return result +} + +func fatalHandler(errFunc func(error)) { + w32.Fatal = errFunc + return +} diff --git a/v3/pkg/application/assets/alpha/index.html b/v3/pkg/application/assets/alpha/index.html new file mode 100644 index 000000000..3c685de17 --- /dev/null +++ b/v3/pkg/application/assets/alpha/index.html @@ -0,0 +1,81 @@ + + + + + Wails Alpha + + + + + + +
Alpha
+
+

Documentation

+

Feedback

+
+ + diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go new file mode 100644 index 000000000..431caab62 --- /dev/null +++ b/v3/pkg/application/bindings.go @@ -0,0 +1,398 @@ +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/internal/hash" + + "github.com/samber/lo" +) + +type CallOptions struct { + MethodID uint32 `json:"methodID"` + MethodName string `json:"methodName"` + Args []json.RawMessage `json:"args"` +} + +type ErrorKind string + +const ( + ReferenceError ErrorKind = "ReferenceError" + TypeError ErrorKind = "TypeError" + RuntimeError ErrorKind = "RuntimeError" +) + +type CallError struct { + Kind ErrorKind `json:"kind"` + Message string `json:"message"` + Cause any `json:"cause,omitempty"` +} + +func (e *CallError) Error() string { + return e.Message +} + +// Parameter defines a Go method parameter +type Parameter struct { + Name string `json:"name,omitempty"` + TypeName string `json:"type"` + ReflectType reflect.Type +} + +func newParameter(Name string, Type reflect.Type) *Parameter { + return &Parameter{ + Name: Name, + TypeName: Type.String(), + ReflectType: Type, + } +} + +// IsType returns true if the given +func (p *Parameter) IsType(typename string) bool { + return p.TypeName == typename +} + +// IsError returns true if the parameter type is an error +func (p *Parameter) IsError() bool { + return p.IsType("error") +} + +// BoundMethod defines all the data related to a Go method that is +// bound to the Wails application +type BoundMethod struct { + ID uint32 `json:"id"` + Name string `json:"name"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + Comments string `json:"comments,omitempty"` + Method reflect.Value `json:"-"` + FQN string + + marshalError func(error) []byte + needsContext bool +} + +type Bindings struct { + marshalError func(error) []byte + boundMethods map[string]*BoundMethod + boundByID map[uint32]*BoundMethod + methodAliases map[uint32]uint32 +} + +func NewBindings(marshalError func(error) []byte, aliases map[uint32]uint32) *Bindings { + return &Bindings{ + marshalError: wrapErrorMarshaler(marshalError, defaultMarshalError), + boundMethods: make(map[string]*BoundMethod), + boundByID: make(map[uint32]*BoundMethod), + methodAliases: aliases, + } +} + +// Add adds the given service to the bindings. +func (b *Bindings) Add(service Service) error { + methods, err := getMethods(service.Instance()) + if err != nil { + return err + } + + marshalError := wrapErrorMarshaler(service.options.MarshalError, defaultMarshalError) + + // Validate and log methods. + for _, method := range methods { + if _, ok := b.boundMethods[method.FQN]; ok { + return fmt.Errorf("bound method '%s' is already registered. Please note that you can register at most one service of each type; additional instances must be wrapped in dedicated structs", method.FQN) + } + if boundMethod, ok := b.boundByID[method.ID]; ok { + return fmt.Errorf("oh wow, we're sorry about this! Amazingly, a hash collision was detected for method '%s' (it generates the same hash as '%s'). To use this method, please rename it. Sorry :(", method.FQN, boundMethod.FQN) + } + + // Log + attrs := []any{"fqn", method.FQN, "id", method.ID} + if alias, ok := lo.FindKey(b.methodAliases, method.ID); ok { + attrs = append(attrs, "alias", alias) + } + globalApplication.debug("Registering bound method:", attrs...) + } + + for _, method := range methods { + // Store composite error marshaler + method.marshalError = marshalError + + // Register method + b.boundMethods[method.FQN] = method + b.boundByID[method.ID] = method + } + + return nil +} + +// Get returns the bound method with the given name +func (b *Bindings) Get(options *CallOptions) *BoundMethod { + return b.boundMethods[options.MethodName] +} + +// GetByID returns the bound method with the given ID +func (b *Bindings) GetByID(id uint32) *BoundMethod { + // Check method aliases + if b.methodAliases != nil { + if alias, ok := b.methodAliases[id]; ok { + id = alias + } + } + + return b.boundByID[id] +} + +// internalServiceMethod is a set of methods +// that are handled specially by the binding engine +// and must not be exposed to the frontend. +// +// For simplicity we exclude these by name +// without checking their signatures, +// and so does the binding generator. +var internalServiceMethods = map[string]bool{ + "ServiceName": true, + "ServiceStartup": true, + "ServiceShutdown": true, + "ServeHTTP": true, +} + +var ctxType = reflect.TypeFor[context.Context]() + +func getMethods(value any) ([]*BoundMethod, error) { + // Create result placeholder + var result []*BoundMethod + + // Check type + if !isNamed(value) { + if isFunction(value) { + name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name() + return nil, fmt.Errorf("%s is a function, not a pointer to named type. Wails v2 has deprecated the binding of functions. Please define your functions as methods on a struct and bind a pointer to that struct", name) + } + + return nil, fmt.Errorf("%s is not a pointer to named type", reflect.ValueOf(value).Type().String()) + } else if !isPtr(value) { + return nil, fmt.Errorf("%s is a named type, not a pointer to named type", reflect.ValueOf(value).Type().String()) + } + + // Process Named Type + namedValue := reflect.ValueOf(value) + ptrType := namedValue.Type() + namedType := ptrType.Elem() + typeName := namedType.Name() + packagePath := namedType.PkgPath() + + if strings.Contains(namedType.String(), "[") { + return nil, fmt.Errorf("%s.%s is a generic type. Generic bound types are not supported", packagePath, namedType.String()) + } + + // Process Methods + for i := range ptrType.NumMethod() { + methodName := ptrType.Method(i).Name + method := namedValue.Method(i) + + if internalServiceMethods[methodName] { + continue + } + + fqn := fmt.Sprintf("%s.%s.%s", packagePath, typeName, methodName) + + // Create new method + boundMethod := &BoundMethod{ + ID: hash.Fnv(fqn), + FQN: fqn, + Name: methodName, + Inputs: nil, + Outputs: nil, + Comments: "", + Method: method, + } + + // Iterate inputs + methodType := method.Type() + inputParamCount := methodType.NumIn() + var inputs []*Parameter + for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ { + input := methodType.In(inputIndex) + if inputIndex == 0 && input.AssignableTo(ctxType) { + boundMethod.needsContext = true + } + thisParam := newParameter("", input) + inputs = append(inputs, thisParam) + } + + boundMethod.Inputs = inputs + + outputParamCount := methodType.NumOut() + var outputs []*Parameter + for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { + output := methodType.Out(outputIndex) + thisParam := newParameter("", output) + outputs = append(outputs, thisParam) + } + boundMethod.Outputs = outputs + + // Save method in result + result = append(result, boundMethod) + + } + + return result, nil +} + +func (b *BoundMethod) String() string { + return b.FQN +} + +var errorType = reflect.TypeFor[error]() + +// Call will attempt to call this bound method with the given args. +// If the call succeeds, result will be either a non-error return value (if there is only one) +// or a slice of non-error return values (if there are more than one). +// +// If the arguments are mistyped or the call returns one or more non-nil error values, +// result is nil and err is an instance of *[CallError]. +func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result any, err error) { + // Use a defer statement to capture panics + defer handlePanic(handlePanicOptions{skipEnd: 5}) + argCount := len(args) + if b.needsContext { + argCount++ + } + + if argCount != len(b.Inputs) { + err = &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount), + } + return + } + + // Convert inputs to values of appropriate type + callArgs := make([]reflect.Value, argCount) + base := 0 + + if b.needsContext { + callArgs[0] = reflect.ValueOf(ctx) + base++ + } + + // Iterate over given arguments + for index, arg := range args { + value := reflect.New(b.Inputs[base+index].ReflectType) + err = json.Unmarshal(arg, value.Interface()) + if err != nil { + err = &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("could not parse argument #%d: %s", index, err), + Cause: json.RawMessage(b.marshalError(err)), + } + return + } + callArgs[base+index] = value.Elem() + } + + // Do the call + var callResults []reflect.Value + if b.Method.Type().IsVariadic() { + callResults = b.Method.CallSlice(callArgs) + } else { + callResults = b.Method.Call(callArgs) + } + + var nonErrorOutputs = make([]any, 0, len(callResults)) + var errorOutputs []error + + for _, field := range callResults { + if field.Type() == errorType { + if field.IsNil() { + continue + } + if errorOutputs == nil { + errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs)) + nonErrorOutputs = nil + } + errorOutputs = append(errorOutputs, field.Interface().(error)) + } else if nonErrorOutputs != nil { + nonErrorOutputs = append(nonErrorOutputs, field.Interface()) + } + } + + if len(errorOutputs) > 0 { + info := make([]json.RawMessage, len(errorOutputs)) + for i, err := range errorOutputs { + info[i] = b.marshalError(err) + } + + cerr := &CallError{ + Kind: RuntimeError, + Message: errors.Join(errorOutputs...).Error(), + Cause: info, + } + if len(info) == 1 { + cerr.Cause = info[0] + } + + err = cerr + } else if len(nonErrorOutputs) == 1 { + result = nonErrorOutputs[0] + } else if len(nonErrorOutputs) > 1 { + result = nonErrorOutputs + } + + return +} + +// wrapErrorMarshaler returns an error marshaling functions +// that calls the primary marshaler first, +// then falls back to the secondary one. +func wrapErrorMarshaler(primary func(error) []byte, secondary func(error) []byte) func(error) []byte { + if primary == nil { + return secondary + } + + return func(err error) []byte { + result := primary(err) + if result == nil { + result = secondary(err) + } + + return result + } +} + +// defaultMarshalError implements the default error marshaling mechanism. +func defaultMarshalError(err error) []byte { + result, jsonErr := json.Marshal(&err) + if jsonErr != nil { + return nil + } + return result +} + +// isPtr returns true if the value given is a pointer. +func isPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr +} + +// isFunction returns true if the given value is a function +func isFunction(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// isNamed returns true if the given value is of named type +// or pointer to named type. +func isNamed(value interface{}) bool { + rv := reflect.ValueOf(value) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + + return rv.Type().Name() != "" +} diff --git a/v3/pkg/application/bindings_test.go b/v3/pkg/application/bindings_test.go new file mode 100644 index 000000000..d76a8efe0 --- /dev/null +++ b/v3/pkg/application/bindings_test.go @@ -0,0 +1,191 @@ +package application_test + +import ( + "context" + "encoding/json" + "errors" + "reflect" + "strings" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type TestService struct { +} + +type Person struct { + Name string `json:"name"` +} + +func (t *TestService) Nil() {} + +func (t *TestService) String(s string) string { + return s +} + +func (t *TestService) Multiple(s string, i int, b bool) (string, int, bool) { + return s, i, b +} + +func (t *TestService) Struct(p Person) Person { + return p +} + +func (t *TestService) StructNil(p Person) (Person, error) { + return p, nil +} + +func (t *TestService) StructError(p Person) (Person, error) { + return p, errors.New("error") +} + +func (t *TestService) Variadic(s ...string) []string { + return s +} + +func (t *TestService) PositionalAndVariadic(a int, _ ...string) int { + return a +} + +func (t *TestService) Slice(a []int) []int { + return a +} + +func newArgs(jsonArgs ...string) (args []json.RawMessage) { + for _, j := range jsonArgs { + args = append(args, json.RawMessage(j)) + } + return +} + +func TestBoundMethodCall(t *testing.T) { + tests := []struct { + name string + method string + args []json.RawMessage + err string + expected interface{} + }{ + { + name: "nil", + method: "Nil", + args: []json.RawMessage{}, + err: "", + expected: nil, + }, + { + name: "string", + method: "String", + args: newArgs(`"foo"`), + err: "", + expected: "foo", + }, + { + name: "multiple", + method: "Multiple", + args: newArgs(`"foo"`, "0", "false"), + err: "", + expected: []interface{}{"foo", 0, false}, + }, + { + name: "struct", + method: "Struct", + args: newArgs(`{ "name": "alice" }`), + err: "", + expected: Person{Name: "alice"}, + }, + { + name: "struct, nil error", + method: "StructNil", + args: newArgs(`{ "name": "alice" }`), + err: "", + expected: Person{Name: "alice"}, + }, + { + name: "struct, error", + method: "StructError", + args: newArgs(`{ "name": "alice" }`), + err: "error", + expected: nil, + }, + { + name: "invalid argument count", + method: "Multiple", + args: newArgs(`"foo"`), + err: "expects 3 arguments, got 1", + expected: nil, + }, + { + name: "invalid argument type", + method: "String", + args: newArgs("1"), + err: "could not parse", + expected: nil, + }, + { + name: "variadic, no arguments", + method: "Variadic", + args: newArgs(`[]`), // variadic parameters are passed as arrays + err: "", + expected: []string{}, + }, + { + name: "variadic", + method: "Variadic", + args: newArgs(`["foo", "bar"]`), + err: "", + expected: []string{"foo", "bar"}, + }, + { + name: "positional and variadic", + method: "PositionalAndVariadic", + args: newArgs("42", `[]`), + err: "", + expected: 42, + }, + { + name: "slice", + method: "Slice", + args: newArgs(`[1,2,3]`), + err: "", + expected: []int{1, 2, 3}, + }, + } + + // init globalApplication + _ = application.New(application.Options{}) + + bindings := application.NewBindings(nil, nil) + + err := bindings.Add(application.NewService(&TestService{})) + if err != nil { + t.Fatalf("bindings.Add() error = %v\n", err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + callOptions := &application.CallOptions{ + MethodName: "github.com/wailsapp/wails/v3/pkg/application_test.TestService." + tt.method, + } + + method := bindings.Get(callOptions) + if method == nil { + t.Fatalf("bound method not found: %s", callOptions.MethodName) + } + + result, err := method.Call(context.TODO(), tt.args) + if (tt.err == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), tt.err)) { + expected := tt.err + if expected == "" { + expected = "nil" + } + t.Fatalf("error: %#v, expected error: %v", err, expected) + } + if !reflect.DeepEqual(result, tt.expected) { + t.Fatalf("result: %v, expected result: %v", result, tt.expected) + } + }) + } + +} diff --git a/v3/pkg/application/browser_manager.go b/v3/pkg/application/browser_manager.go new file mode 100644 index 000000000..3fe81720a --- /dev/null +++ b/v3/pkg/application/browser_manager.go @@ -0,0 +1,27 @@ +package application + +import ( + "github.com/pkg/browser" +) + +// BrowserManager manages browser-related operations +type BrowserManager struct { + app *App +} + +// newBrowserManager creates a new BrowserManager instance +func newBrowserManager(app *App) *BrowserManager { + return &BrowserManager{ + app: app, + } +} + +// OpenURL opens a URL in the default browser +func (bm *BrowserManager) OpenURL(url string) error { + return browser.OpenURL(url) +} + +// OpenFile opens a file in the default browser +func (bm *BrowserManager) OpenFile(path string) error { + return browser.OpenFile(path) +} diff --git a/v3/pkg/application/clipboard.go b/v3/pkg/application/clipboard.go new file mode 100644 index 000000000..f21b597e2 --- /dev/null +++ b/v3/pkg/application/clipboard.go @@ -0,0 +1,26 @@ +package application + +type clipboardImpl interface { + setText(text string) bool + text() (string, bool) +} + +type Clipboard struct { + impl clipboardImpl +} + +func newClipboard() *Clipboard { + return &Clipboard{ + impl: newClipboardImpl(), + } +} + +func (c *Clipboard) SetText(text string) bool { + return InvokeSyncWithResult(func() bool { + return c.impl.setText(text) + }) +} + +func (c *Clipboard) Text() (string, bool) { + return InvokeSyncWithResultAndOther(c.impl.text) +} diff --git a/v3/pkg/application/clipboard_darwin.go b/v3/pkg/application/clipboard_darwin.go new file mode 100644 index 000000000..3c0c80ac6 --- /dev/null +++ b/v3/pkg/application/clipboard_darwin.go @@ -0,0 +1,56 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#import +#import + +bool setClipboardText(const char* text) { + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + NSError *error = nil; + NSString *string = [NSString stringWithUTF8String:text]; + [pasteBoard clearContents]; + return [pasteBoard setString:string forType:NSPasteboardTypeString]; +} + +const char* getClipboardText() { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *text = [pasteboard stringForType:NSPasteboardTypeString]; + return [text UTF8String]; +} + +*/ +import "C" +import ( + "sync" + "unsafe" +) + +var clipboardLock sync.RWMutex + +type macosClipboard struct{} + +func (m macosClipboard) setText(text string) bool { + clipboardLock.Lock() + defer clipboardLock.Unlock() + cText := C.CString(text) + success := C.setClipboardText(cText) + C.free(unsafe.Pointer(cText)) + return bool(success) +} + +func (m macosClipboard) text() (string, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + clipboardText := C.getClipboardText() + result := C.GoString(clipboardText) + return result, true +} + +func newClipboardImpl() *macosClipboard { + return &macosClipboard{} +} diff --git a/v3/pkg/application/clipboard_linux.go b/v3/pkg/application/clipboard_linux.go new file mode 100644 index 000000000..1c662cd6f --- /dev/null +++ b/v3/pkg/application/clipboard_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package application + +import ( + "sync" +) + +var clipboardLock sync.RWMutex + +type linuxClipboard struct{} + +func (m linuxClipboard) setText(text string) bool { + clipboardLock.Lock() + defer clipboardLock.Unlock() + clipboardSet(text) + return true +} + +func (m linuxClipboard) text() (string, bool) { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + return clipboardGet(), true +} + +func newClipboardImpl() *linuxClipboard { + return &linuxClipboard{} +} diff --git a/v3/pkg/application/clipboard_manager.go b/v3/pkg/application/clipboard_manager.go new file mode 100644 index 000000000..abd0b19c1 --- /dev/null +++ b/v3/pkg/application/clipboard_manager.go @@ -0,0 +1,32 @@ +package application + +// ClipboardManager manages clipboard operations +type ClipboardManager struct { + app *App + clipboard *Clipboard +} + +// newClipboardManager creates a new ClipboardManager instance +func newClipboardManager(app *App) *ClipboardManager { + return &ClipboardManager{ + app: app, + } +} + +// SetText sets text in the clipboard +func (cm *ClipboardManager) SetText(text string) bool { + return cm.getClipboard().SetText(text) +} + +// Text gets text from the clipboard +func (cm *ClipboardManager) Text() (string, bool) { + return cm.getClipboard().Text() +} + +// getClipboard returns the clipboard instance, creating it if needed (lazy initialization) +func (cm *ClipboardManager) getClipboard() *Clipboard { + if cm.clipboard == nil { + cm.clipboard = newClipboard() + } + return cm.clipboard +} diff --git a/v3/pkg/application/clipboard_windows.go b/v3/pkg/application/clipboard_windows.go new file mode 100644 index 000000000..507eac3da --- /dev/null +++ b/v3/pkg/application/clipboard_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "sync" +) + +type windowsClipboard struct { + lock sync.RWMutex +} + +func (m *windowsClipboard) setText(text string) bool { + m.lock.Lock() + defer m.lock.Unlock() + return w32.SetClipboardText(text) == nil +} + +func (m *windowsClipboard) text() (string, bool) { + m.lock.Lock() + defer m.lock.Unlock() + text, err := w32.GetClipboardText() + return text, err == nil +} + +func newClipboardImpl() *windowsClipboard { + return &windowsClipboard{} +} diff --git a/v3/pkg/application/context.go b/v3/pkg/application/context.go new file mode 100644 index 000000000..8ed2cb792 --- /dev/null +++ b/v3/pkg/application/context.go @@ -0,0 +1,62 @@ +package application + +type Context struct { + // contains filtered or unexported fields + data map[string]any +} + +func newContext() *Context { + return &Context{ + data: make(map[string]any), + } +} + +const ( + clickedMenuItem string = "clickedMenuItem" + menuItemIsChecked string = "menuItemIsChecked" + contextMenuData string = "contextMenuData" +) + +func (c *Context) ClickedMenuItem() *MenuItem { + result, exists := c.data[clickedMenuItem] + if !exists { + return nil + } + return result.(*MenuItem) +} + +func (c *Context) IsChecked() bool { + result, exists := c.data[menuItemIsChecked] + if !exists { + return false + } + return result.(bool) +} +func (c *Context) ContextMenuData() string { + result := c.data[contextMenuData] + if result == nil { + return "" + } + str, ok := result.(string) + if !ok { + return "" + } + return str +} + +func (c *Context) withClickedMenuItem(menuItem *MenuItem) *Context { + c.data[clickedMenuItem] = menuItem + return c +} + +func (c *Context) withChecked(checked bool) { + c.data[menuItemIsChecked] = checked +} + +func (c *Context) withContextMenuData(data *ContextMenuData) *Context { + if data == nil { + return c + } + c.data[contextMenuData] = data.Data + return c +} diff --git a/v3/pkg/application/context_application_event.go b/v3/pkg/application/context_application_event.go new file mode 100644 index 000000000..32f392455 --- /dev/null +++ b/v3/pkg/application/context_application_event.go @@ -0,0 +1,106 @@ +package application + +import "log" + +var blankApplicationEventContext = &ApplicationEventContext{} + +const ( + CONTEXT_OPENED_FILES = "openedFiles" + CONTEXT_FILENAME = "filename" + CONTEXT_URL = "url" +) + +// ApplicationEventContext is the context of an application event +type ApplicationEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +// OpenedFiles returns the opened files from the event context if it was set +func (c ApplicationEventContext) OpenedFiles() []string { + files, ok := c.data[CONTEXT_OPENED_FILES] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c ApplicationEventContext) setOpenedFiles(files []string) { + c.data[CONTEXT_OPENED_FILES] = files +} + +func (c ApplicationEventContext) setIsDarkMode(mode bool) { + c.data["isDarkMode"] = mode +} + +func (c ApplicationEventContext) getBool(key string) bool { + mode, ok := c.data[key] + if !ok { + return false + } + result, ok := mode.(bool) + if !ok { + return false + } + return result +} + +// IsDarkMode returns true if the event context has a dark mode +func (c ApplicationEventContext) IsDarkMode() bool { + return c.getBool("isDarkMode") +} + +// HasVisibleWindows returns true if the event context has a visible window +func (c ApplicationEventContext) HasVisibleWindows() bool { + return c.getBool("hasVisibleWindows") +} + +func (c *ApplicationEventContext) setData(data map[string]any) { + c.data = data +} + +func (c *ApplicationEventContext) setOpenedWithFile(filepath string) { + c.data[CONTEXT_FILENAME] = filepath +} + +func (c *ApplicationEventContext) setURL(openedWithURL string) { + c.data[CONTEXT_URL] = openedWithURL +} + +// Filename returns the filename from the event context if it was set +func (c ApplicationEventContext) Filename() string { + filename, ok := c.data[CONTEXT_FILENAME] + if !ok { + return "" + } + result, ok := filename.(string) + if !ok { + return "" + } + return result +} + +// URL returns the URL from the event context if it was set +func (c ApplicationEventContext) URL() string { + url, ok := c.data[CONTEXT_URL] + if !ok { + log.Println("URL not found in event context") + return "" + } + result, ok := url.(string) + if !ok { + log.Println("URL not a string in event context") + return "" + } + return result +} + +func newApplicationEventContext() *ApplicationEventContext { + return &ApplicationEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/context_menu_manager.go b/v3/pkg/application/context_menu_manager.go new file mode 100644 index 000000000..669e8aca5 --- /dev/null +++ b/v3/pkg/application/context_menu_manager.go @@ -0,0 +1,54 @@ +package application + +// ContextMenuManager manages all context menu operations +type ContextMenuManager struct { + app *App +} + +// newContextMenuManager creates a new ContextMenuManager instance +func newContextMenuManager(app *App) *ContextMenuManager { + return &ContextMenuManager{ + app: app, + } +} + +// New creates a new context menu +func (cmm *ContextMenuManager) New() *ContextMenu { + return &ContextMenu{ + Menu: NewMenu(), + } +} + +// Add adds a context menu (replaces Register for consistency) +func (cmm *ContextMenuManager) Add(name string, menu *ContextMenu) { + cmm.app.contextMenusLock.Lock() + defer cmm.app.contextMenusLock.Unlock() + cmm.app.contextMenus[name] = menu +} + +// Remove removes a context menu by name (replaces Unregister for consistency) +func (cmm *ContextMenuManager) Remove(name string) { + cmm.app.contextMenusLock.Lock() + defer cmm.app.contextMenusLock.Unlock() + delete(cmm.app.contextMenus, name) +} + +// Get retrieves a context menu by name +func (cmm *ContextMenuManager) Get(name string) (*ContextMenu, bool) { + cmm.app.contextMenusLock.RLock() + defer cmm.app.contextMenusLock.RUnlock() + menu, exists := cmm.app.contextMenus[name] + return menu, exists +} + +// GetAll returns all registered context menus as a slice +func (cmm *ContextMenuManager) GetAll() []*ContextMenu { + cmm.app.contextMenusLock.RLock() + defer cmm.app.contextMenusLock.RUnlock() + + result := make([]*ContextMenu, 0, len(cmm.app.contextMenus)) + for _, menu := range cmm.app.contextMenus { + result = append(result, menu) + } + return result +} diff --git a/v3/pkg/application/context_window_event.go b/v3/pkg/application/context_window_event.go new file mode 100644 index 000000000..f5063b19d --- /dev/null +++ b/v3/pkg/application/context_window_event.go @@ -0,0 +1,34 @@ +package application + +var blankWindowEventContext = &WindowEventContext{} + +const ( + droppedFiles = "droppedFiles" +) + +type WindowEventContext struct { + // contains filtered or unexported fields + data map[string]any +} + +func (c WindowEventContext) DroppedFiles() []string { + files, ok := c.data[droppedFiles] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +func (c WindowEventContext) setDroppedFiles(files []string) { + c.data[droppedFiles] = files +} + +func newWindowEventContext() *WindowEventContext { + return &WindowEventContext{ + data: make(map[string]any), + } +} diff --git a/v3/pkg/application/dialog_manager.go b/v3/pkg/application/dialog_manager.go new file mode 100644 index 000000000..fa49392ae --- /dev/null +++ b/v3/pkg/application/dialog_manager.go @@ -0,0 +1,57 @@ +package application + +// DialogManager manages dialog-related operations +type DialogManager struct { + app *App +} + +// newDialogManager creates a new DialogManager instance +func newDialogManager(app *App) *DialogManager { + return &DialogManager{ + app: app, + } +} + +// OpenFile creates a file dialog for selecting files +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct { + return OpenFileDialog() +} + +// OpenFileWithOptions creates a file dialog with options +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { + result := OpenFileDialog() + result.SetOptions(options) + return result +} + +// SaveFile creates a save file dialog +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct { + return SaveFileDialog() +} + +// SaveFileWithOptions creates a save file dialog with options +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct { + result := SaveFileDialog() + result.SetOptions(options) + return result +} + +// Info creates an information dialog +func (dm *DialogManager) Info() *MessageDialog { + return InfoDialog() +} + +// Question creates a question dialog +func (dm *DialogManager) Question() *MessageDialog { + return QuestionDialog() +} + +// Warning creates a warning dialog +func (dm *DialogManager) Warning() *MessageDialog { + return WarningDialog() +} + +// Error creates an error dialog +func (dm *DialogManager) Error() *MessageDialog { + return ErrorDialog() +} diff --git a/v3/pkg/application/dialogs.go b/v3/pkg/application/dialogs.go new file mode 100644 index 000000000..2c832d5f2 --- /dev/null +++ b/v3/pkg/application/dialogs.go @@ -0,0 +1,494 @@ +package application + +import ( + "strings" + "sync" +) + +type DialogType int + +var dialogMapID = make(map[uint]struct{}) +var dialogIDLock sync.RWMutex + +func getDialogID() uint { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + var dialogID uint + for { + if _, ok := dialogMapID[dialogID]; !ok { + dialogMapID[dialogID] = struct{}{} + break + } + dialogID++ + if dialogID == 0 { + panic("no more dialog IDs") + } + } + return dialogID +} + +func freeDialogID(id uint) { + dialogIDLock.Lock() + defer dialogIDLock.Unlock() + delete(dialogMapID, id) +} + +var openFileResponses = make(map[uint]chan string) +var saveFileResponses = make(map[uint]chan string) + +const ( + InfoDialogType DialogType = iota + QuestionDialogType + WarningDialogType + ErrorDialogType +) + +type Button struct { + Label string + IsCancel bool + IsDefault bool + Callback func() +} + +func (b *Button) OnClick(callback func()) *Button { + b.Callback = callback + return b +} + +func (b *Button) SetAsDefault() *Button { + b.IsDefault = true + return b +} + +func (b *Button) SetAsCancel() *Button { + b.IsCancel = true + return b +} + +type messageDialogImpl interface { + show() +} + +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte + window *WebviewWindow +} + +type MessageDialog struct { + MessageDialogOptions + + // platform independent + impl messageDialogImpl +} + +var defaultTitles = map[DialogType]string{ + InfoDialogType: "Information", + QuestionDialogType: "Question", + WarningDialogType: "Warning", + ErrorDialogType: "Error", +} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: dialogType, + }, + impl: nil, + } +} + +func (d *MessageDialog) SetTitle(title string) *MessageDialog { + d.Title = title + return d +} + +func (d *MessageDialog) Show() { + if d.impl == nil { + d.impl = newDialogImpl(d) + } + InvokeSync(d.impl.show) +} + +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog { + d.Icon = icon + return d +} + +func (d *MessageDialog) AddButton(s string) *Button { + result := &Button{ + Label: s, + } + d.Buttons = append(d.Buttons, result) + return result +} + +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog { + d.Buttons = buttons + return d +} + +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog { + d.window = window.(*WebviewWindow) + return d +} + +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsDefault = false + } + button.IsDefault = true + return d +} + +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsCancel = false + } + button.IsCancel = true + return d +} + +func (d *MessageDialog) SetMessage(message string) *MessageDialog { + d.Message = message + return d +} + +type openFileDialogImpl interface { + show() (chan string, error) +} + +type FileFilter struct { + DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)" + Pattern string // semicolon separated list of extensions, EG: "*.jpg;*.png" +} + +type OpenFileDialogOptions struct { + CanChooseDirectories bool + CanChooseFiles bool + CanCreateDirectories bool + ShowHiddenFiles bool + ResolvesAliases bool + AllowsMultipleSelection bool + HideExtension bool + CanSelectHiddenExtension bool + TreatsFilePackagesAsDirectories bool + AllowsOtherFileTypes bool + Filters []FileFilter + Window *WebviewWindow + + Title string + Message string + ButtonText string + Directory string +} + +type OpenFileDialogStruct struct { + id uint + canChooseDirectories bool + canChooseFiles bool + canCreateDirectories bool + showHiddenFiles bool + resolvesAliases bool + allowsMultipleSelection bool + hideExtension bool + canSelectHiddenExtension bool + treatsFilePackagesAsDirectories bool + allowsOtherFileTypes bool + filters []FileFilter + + title string + message string + buttonText string + directory string + window *WebviewWindow + + impl openFileDialogImpl +} + +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct { + d.canChooseFiles = canChooseFiles + return d +} + +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct { + d.canChooseDirectories = canChooseDirectories + return d +} + +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct { + d.allowsOtherFileTypes = allowsOtherFileTypes + return d +} + +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} + +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct { + d.window = window.(*WebviewWindow) + return d +} + +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct { + d.resolvesAliases = resolvesAliases + return d +} + +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct { + d.title = title + return d +} + +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { + d.allowsMultipleSelection = false + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + + return result, err +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { + d.allowsMultipleSelection = true + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err != nil { + return nil, err + } + + var result []string + for filename := range selections { + result = append(result, filename) + } + + return result, err +} + +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct { + d.message = message + return d +} + +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct { + d.buttonText = text + return d +} + +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct { + d.directory = directory + return d +} + +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) { + d.title = options.Title + d.message = options.Message + d.buttonText = options.ButtonText + d.directory = options.Directory + d.canChooseDirectories = options.CanChooseDirectories + d.canChooseFiles = options.CanChooseFiles + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.resolvesAliases = options.ResolvesAliases + d.allowsMultipleSelection = options.AllowsMultipleSelection + d.hideExtension = options.HideExtension + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.allowsOtherFileTypes = options.AllowsOtherFileTypes + d.filters = options.Filters + d.window = options.Window +} + +func newOpenFileDialog() *OpenFileDialogStruct { + return &OpenFileDialogStruct{ + id: getDialogID(), + canChooseDirectories: false, + canChooseFiles: true, + canCreateDirectories: true, + resolvesAliases: false, + } +} + +func newSaveFileDialog() *SaveFileDialogStruct { + return &SaveFileDialogStruct{ + id: getDialogID(), + canCreateDirectories: true, + } +} + +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Title string + Message string + Directory string + Filename string + ButtonText string + Filters []FileFilter + Window *WebviewWindow +} + +type SaveFileDialogStruct struct { + id uint + canCreateDirectories bool + showHiddenFiles bool + canSelectHiddenExtension bool + allowOtherFileTypes bool + hideExtension bool + treatsFilePackagesAsDirectories bool + message string + directory string + filename string + buttonText string + filters []FileFilter + + window *WebviewWindow + + impl saveFileDialogImpl + title string +} + +type saveFileDialogImpl interface { + show() (chan string, error) +} + +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) { + d.title = options.Title + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.allowOtherFileTypes = options.AllowOtherFileTypes + d.hideExtension = options.HideExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.message = options.Message + d.directory = options.Directory + d.filename = options.Filename + d.buttonText = options.ButtonText + d.filters = options.Filters + d.window = options.Window +} + +// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions. +// EG: AddFilter("Image Files", "*.jpg;*.png") +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct { + d.message = message + return d +} + +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct { + d.directory = directory + return d +} + +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct { + d.window = window.(*WebviewWindow) + return d +} + +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { + if d.impl == nil { + d.impl = newSaveFileDialogImpl(d) + } + + var result string + selections, err := InvokeSyncWithResultAndError(d.impl.show) + if err == nil { + result = <-selections + } + return result, err +} + +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct { + d.buttonText = text + return d +} + +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct { + d.filename = filename + return d +} + +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct { + d.allowOtherFileTypes = allowOtherFileTypes + return d +} + +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} diff --git a/v3/pkg/application/dialogs_darwin.go b/v3/pkg/application/dialogs_darwin.go new file mode 100644 index 000000000..a5c9b43c3 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin.go @@ -0,0 +1,591 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 -framework UniformTypeIdentifiers + +#import + +#import +#import "dialogs_darwin_delegate.h" + +extern void openFileDialogCallback(uint id, char* path); +extern void openFileDialogCallbackEnd(uint id); +extern void saveFileDialogCallback(uint id, char* path); +extern void dialogCallback(int id, int buttonPressed); + +static void showAboutBox(char* title, char *message, void *icon, int length) { + + // run on main thread + NSAlert *alert = [[NSAlert alloc] init]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } + [alert setAlertStyle:NSAlertStyleInformational]; + [alert runModal]; +} + + +// Create an NSAlert +static void* createAlert(int alertType, char* title, char *message, void *icon, int length) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:alertType]; + if (title != NULL) { + [alert setMessageText:[NSString stringWithUTF8String:title]]; + free(title); + } + if (message != NULL) { + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + free(message); + } + if (icon != NULL) { + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [alert setIcon:image]; + } else { + if(alertType == NSAlertStyleCritical || alertType == NSAlertStyleWarning) { + NSImage *image = [NSImage imageNamed:NSImageNameCaution]; + [alert setIcon:image]; + } else { + NSImage *image = [NSImage imageNamed:NSImageNameInfo]; + [alert setIcon:image]; + } + } + return alert; + +} + +static int getButtonNumber(NSModalResponse response) { + int buttonNumber = 0; + if( response == NSAlertFirstButtonReturn ) { + buttonNumber = 0; + } + else if( response == NSAlertSecondButtonReturn ) { + buttonNumber = 1; + } + else if( response == NSAlertThirdButtonReturn ) { + buttonNumber = 2; + } else { + buttonNumber = 3; + } + return buttonNumber; +} + +// Run the dialog +static void dialogRunModal(void *dialog, void *parent, int callBackID) { + NSAlert *alert = (__bridge NSAlert *)dialog; + + // If the parent is NULL, we are running a modal dialog, otherwise attach the alert to the parent + if( parent == NULL ) { + NSModalResponse response = [alert runModal]; + int returnCode = getButtonNumber(response); + dialogCallback(callBackID, returnCode); + } else { + NSWindow *window = (__bridge NSWindow *)parent; + [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse response) { + int returnCode = getButtonNumber(response); + dialogCallback(callBackID, returnCode); + }]; + } +} + +// Release the dialog +static void releaseDialog(void *dialog) { + NSAlert *alert = (__bridge NSAlert *)dialog; + [alert release]; +} + +// Add a button to the dialog +static void alertAddButton(void *dialog, char *label, bool isDefault, bool isCancel) { + NSAlert *alert = (__bridge NSAlert *)dialog; + NSButton *button = [alert addButtonWithTitle:[NSString stringWithUTF8String:label]]; + free(label); + if( isDefault ) { + [button setKeyEquivalent:@"\r"]; + } else if( isCancel ) { + [button setKeyEquivalent:@"\033"]; + } else { + [button setKeyEquivalent:@""]; + } +} + +static void processOpenFileDialogResults(NSOpenPanel *panel, NSInteger result, uint dialogID) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSArray *urls = [panel URLs]; + if ([urls count] > 0) { + NSArray *urls = [panel URLs]; + for (NSURL *url in urls) { + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } else { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + openFileDialogCallback(dialogID, (char *)path); + } + } + openFileDialogCallbackEnd(dialogID); +} + + +static void showOpenFileDialog(unsigned int dialogID, + bool canChooseFiles, + bool canChooseDirectories, + bool canCreateDirectories, + bool showHiddenFiles, + bool allowsMultipleSelection, + bool resolvesAliases, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowsOtherFileTypes, + char *filterPatterns, + unsigned int filterPatternsCount, + char* message, + char* directory, + char* buttonText, + + void *window) { + + // run on main thread + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + // print out filterPatterns if length > 0 + if (filterPatternsCount > 0) { + OpenPanelDelegate *delegate = [[OpenPanelDelegate alloc] init]; + [panel setDelegate:delegate]; + // Initialise NSString with bytes and UTF8 encoding + NSString *filterPatternsString = [[NSString alloc] initWithBytes:filterPatterns length:filterPatternsCount encoding:NSUTF8StringEncoding]; + // Convert NSString to NSArray + delegate.allowedExtensions = [filterPatternsString componentsSeparatedByString:@";"]; + + // Use UTType if macOS 11 or higher to add file filters +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 + if (@available(macOS 11, *)) { + NSMutableArray *filterTypes = [NSMutableArray array]; + // Iterate the filtertypes, create uti's that are limited to the file extensions then add + for (NSString *filterType in delegate.allowedExtensions) { + [filterTypes addObject:[UTType typeWithFilenameExtension:filterType]]; + } + [panel setAllowedContentTypes:filterTypes]; + } +#else + [panel setAllowedFileTypes:delegate.allowedExtensions]; +#endif + + // Free the memory + free(filterPatterns); + } + + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanChooseFiles:canChooseFiles]; + [panel setCanChooseDirectories:canChooseDirectories]; + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setAllowsMultipleSelection:allowsMultipleSelection]; + [panel setResolvesAliases:resolvesAliases]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowsOtherFileTypes]; + + + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + processOpenFileDialogResults(panel, result, dialogID); + }]; + } +} + +static void showSaveFileDialog(unsigned int dialogID, + bool canCreateDirectories, + bool showHiddenFiles, + bool canSelectHiddenExtension, + bool hideExtension, + bool treatsFilePackagesAsDirectories, + bool allowOtherFileTypes, + char* message, + char* directory, + char* buttonText, + char* filename, + void *window) { + + NSSavePanel *panel = [NSSavePanel savePanel]; + + if (message != NULL) { + [panel setMessage:[NSString stringWithUTF8String:message]]; + free(message); + } + + if (directory != NULL) { + [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:directory]]]; + free(directory); + } + + if (filename != NULL) { + [panel setNameFieldStringValue:[NSString stringWithUTF8String:filename]]; + free(filename); + } + + if (buttonText != NULL) { + [panel setPrompt:[NSString stringWithUTF8String:buttonText]]; + free(buttonText); + } + + [panel setCanCreateDirectories:canCreateDirectories]; + [panel setShowsHiddenFiles:showHiddenFiles]; + [panel setCanSelectHiddenExtension:canSelectHiddenExtension]; + [panel setExtensionHidden:hideExtension]; + [panel setTreatsFilePackagesAsDirectories:treatsFilePackagesAsDirectories]; + [panel setAllowsOtherFileTypes:allowOtherFileTypes]; + + if (window != NULL) { + [panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } +} + +*/ +import "C" +import ( + "strings" + "sync" + "unsafe" +) + +const NSAlertStyleWarning = C.int(0) +const NSAlertStyleInformational = C.int(1) +const NSAlertStyleCritical = C.int(2) + +var alertTypeMap = map[DialogType]C.int{ + WarningDialogType: NSAlertStyleWarning, + InfoDialogType: NSAlertStyleInformational, + ErrorDialogType: NSAlertStyleCritical, + QuestionDialogType: NSAlertStyleInformational, +} + +type dialogResultCallback func(int) + +var ( + callbacks = make(map[int]dialogResultCallback) + mutex = &sync.Mutex{} +) + +func addDialogCallback(callback dialogResultCallback) int { + mutex.Lock() + defer mutex.Unlock() + + // Find the first free integer key + var id int + for { + if _, exists := callbacks[id]; !exists { + break + } + id++ + } + + // Save the function in the map using the integer key + callbacks[id] = callback + + // Return the key + return id +} + +func removeDialogCallback(id int) { + mutex.Lock() + defer mutex.Unlock() + delete(callbacks, id) +} + +//export dialogCallback +func dialogCallback(id C.int, buttonPressed C.int) { + mutex.Lock() + callback, exists := callbacks[int(id)] + mutex.Unlock() + + if !exists { + return + } + + // Call the function with the button number + callback(int(buttonPressed)) // Replace nil with the actual slice of buttons +} + +func (m *macosApp) showAboutDialog(title string, message string, icon []byte) { + var iconData unsafe.Pointer + if icon != nil { + iconData = unsafe.Pointer(&icon[0]) + } + InvokeAsync(func() { + C.showAboutBox(C.CString(title), C.CString(message), iconData, C.int(len(icon))) + }) +} + +type macosDialog struct { + dialog *MessageDialog + + nsDialog unsafe.Pointer +} + +func (m *macosDialog) show() { + InvokeAsync(func() { + + // Mac can only have 4 Buttons on a dialog + if len(m.dialog.Buttons) > 4 { + m.dialog.Buttons = m.dialog.Buttons[:4] + } + + if m.nsDialog != nil { + C.releaseDialog(m.nsDialog) + } + var title *C.char + if m.dialog.Title != "" { + title = C.CString(m.dialog.Title) + } + var message *C.char + if m.dialog.Message != "" { + message = C.CString(m.dialog.Message) + } + var iconData unsafe.Pointer + var iconLength C.int + if m.dialog.Icon != nil { + iconData = unsafe.Pointer(&m.dialog.Icon[0]) + iconLength = C.int(len(m.dialog.Icon)) + } else { + // if it's an error, use the application Icon + if m.dialog.DialogType == ErrorDialogType { + if globalApplication.options.Icon != nil { + iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) + iconLength = C.int(len(globalApplication.options.Icon)) + } + } + } + var parent unsafe.Pointer + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + parent = unsafe.Pointer(window) + } + + alertType, ok := alertTypeMap[m.dialog.DialogType] + if !ok { + alertType = C.NSAlertStyleInformational + } + + m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength) + + // Reverse the Buttons so that the default is on the right + reversedButtons := make([]*Button, len(m.dialog.Buttons)) + var count = 0 + for i := len(m.dialog.Buttons) - 1; i >= 0; i-- { + button := m.dialog.Buttons[i] + C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel)) + reversedButtons[count] = m.dialog.Buttons[i] + count++ + } + + var callBackID int + callBackID = addDialogCallback(func(buttonPressed int) { + if len(m.dialog.Buttons) > buttonPressed { + button := reversedButtons[buttonPressed] + if button.Callback != nil { + button.Callback() + } + } + removeDialogCallback(callBackID) + }) + + C.dialogRunModal(m.nsDialog, parent, C.int(callBackID)) + + }) + +} + +func newDialogImpl(d *MessageDialog) *macosDialog { + return &macosDialog{ + dialog: d, + } +} + +type macosOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *macosOpenFileDialog { + return &macosOpenFileDialog{ + dialog: d, + } +} + +func toCString(s string) *C.char { + if s == "" { + return nil + } + return C.CString(s) +} + +func (m *macosOpenFileDialog) show() (chan string, error) { + openFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + nsWindow = unsafe.Pointer(window) + } + + // Massage filter patterns into macOS format + // We iterate all filter patterns, tidy them up and then join them with a semicolon + // This should produce a single string of extensions like "png;jpg;gif" + var filterPatterns string + if len(m.dialog.filters) > 0 { + var allPatterns []string + for _, filter := range m.dialog.filters { + patternComponents := strings.Split(filter.Pattern, ";") + for i, component := range patternComponents { + filterPattern := strings.TrimSpace(component) + filterPattern = strings.TrimPrefix(filterPattern, "*.") + patternComponents[i] = filterPattern + } + allPatterns = append(allPatterns, strings.Join(patternComponents, ";")) + } + filterPatterns = strings.Join(allPatterns, ";") + } + C.showOpenFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canChooseFiles), + C.bool(m.dialog.canChooseDirectories), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.allowsMultipleSelection), + C.bool(m.dialog.resolvesAliases), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowsOtherFileTypes), + toCString(filterPatterns), + C.uint(len(filterPatterns)), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + nsWindow) + + return openFileResponses[m.dialog.id], nil +} + +//export openFileDialogCallback +func openFileDialogCallback(cid C.uint, cpath *C.char) { + path := C.GoString(cpath) + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + channel <- path + } else { + panic("No channel found for open file dialog") + } +} + +//export openFileDialogCallbackEnd +func openFileDialogCallbackEnd(cid C.uint) { + id := uint(cid) + channel, ok := openFileResponses[id] + if ok { + close(channel) + delete(openFileResponses, id) + freeDialogID(id) + } else { + panic("No channel found for open file dialog") + } +} + +type macosSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *macosSaveFileDialog { + return &macosSaveFileDialog{ + dialog: d, + } +} + +func (m *macosSaveFileDialog) show() (chan string, error) { + saveFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + window, _ := m.dialog.window.NativeWindowHandle() + nsWindow = unsafe.Pointer(window) + } + C.showSaveFileDialog(C.uint(m.dialog.id), + C.bool(m.dialog.canCreateDirectories), + C.bool(m.dialog.showHiddenFiles), + C.bool(m.dialog.canSelectHiddenExtension), + C.bool(m.dialog.hideExtension), + C.bool(m.dialog.treatsFilePackagesAsDirectories), + C.bool(m.dialog.allowOtherFileTypes), + toCString(m.dialog.message), + toCString(m.dialog.directory), + toCString(m.dialog.buttonText), + toCString(m.dialog.filename), + nsWindow) + return saveFileResponses[m.dialog.id], nil +} + +//export saveFileDialogCallback +func saveFileDialogCallback(cid C.uint, cpath *C.char) { + // Covert the path to a string + path := C.GoString(cpath) + id := uint(cid) + // put response on channel + channel, ok := saveFileResponses[id] + if ok { + channel <- path + close(channel) + delete(saveFileResponses, id) + freeDialogID(id) + + } else { + panic("No channel found for save file dialog") + } +} diff --git a/v3/pkg/application/dialogs_darwin_delegate.h b/v3/pkg/application/dialogs_darwin_delegate.h new file mode 100644 index 000000000..d1c732a91 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.h @@ -0,0 +1,18 @@ +//go:build darwin + +#ifndef _DIALOGS_DELEGATE_H_ +#define _DIALOGS_DELEGATE_H_ + +#import + +// Conditionally import UniformTypeIdentifiers based on OS version +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 +#import +#endif + +// OpenPanel delegate to handle file filtering +@interface OpenPanelDelegate : NSObject +@property (nonatomic, strong) NSArray *allowedExtensions; +@end + +#endif \ No newline at end of file diff --git a/v3/pkg/application/dialogs_darwin_delegate.m b/v3/pkg/application/dialogs_darwin_delegate.m new file mode 100644 index 000000000..284f98ab8 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin_delegate.m @@ -0,0 +1,38 @@ +//go:build darwin + +#import "dialogs_darwin_delegate.h" + +// Override shouldEnableURL +@implementation OpenPanelDelegate +- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { + if (url == nil) { + return NO; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDirectory = NO; + if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) { + return YES; + } + + // If no extensions specified, allow all files + if (self.allowedExtensions == nil || [self.allowedExtensions count] == 0) { + return YES; + } + + NSString *extension = [url.pathExtension lowercaseString]; + if (extension == nil || [extension isEqualToString:@""]) { + return NO; + } + + // Check if the extension is in our allowed list (case insensitive) + for (NSString *allowedExt in self.allowedExtensions) { + if ([[allowedExt lowercaseString] isEqualToString:extension]) { + return YES; + } + } + + return NO; +} + +@end diff --git a/v3/pkg/application/dialogs_linux.go b/v3/pkg/application/dialogs_linux.go new file mode 100644 index 000000000..5573e739c --- /dev/null +++ b/v3/pkg/application/dialogs_linux.go @@ -0,0 +1,79 @@ +package application + +func (a *linuxApp) showAboutDialog(title string, message string, icon []byte) { + window, _ := globalApplication.Window.GetByID(a.getCurrentWindowID()) + var parent uintptr + if window != nil { + parent, _ = window.(*WebviewWindow).NativeWindowHandle() + } + about := newMessageDialog(InfoDialogType) + about.SetTitle(title). + SetMessage(message). + SetIcon(icon) + InvokeAsync(func() { + runQuestionDialog( + pointer(parent), + about, + ) + }) +} + +type linuxDialog struct { + dialog *MessageDialog +} + +func (m *linuxDialog) show() { + windowId := getNativeApplication().getCurrentWindowID() + window, _ := globalApplication.Window.GetByID(windowId) + var parent uintptr + if window != nil { + parent, _ = window.(*WebviewWindow).NativeWindowHandle() + } + + InvokeAsync(func() { + response := runQuestionDialog(pointer(parent), m.dialog) + if response >= 0 && response < len(m.dialog.Buttons) { + button := m.dialog.Buttons[response] + if button.Callback != nil { + go func() { + defer handlePanic() + button.Callback() + }() + } + } + }) +} + +func newDialogImpl(d *MessageDialog) *linuxDialog { + return &linuxDialog{ + dialog: d, + } +} + +type linuxOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *linuxOpenFileDialog { + return &linuxOpenFileDialog{ + dialog: d, + } +} + +func (m *linuxOpenFileDialog) show() (chan string, error) { + return runOpenFileDialog(m.dialog) +} + +type linuxSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *linuxSaveFileDialog { + return &linuxSaveFileDialog{ + dialog: d, + } +} + +func (m *linuxSaveFileDialog) show() (chan string, error) { + return runSaveFileDialog(m.dialog) +} diff --git a/v3/pkg/application/dialogs_windows.go b/v3/pkg/application/dialogs_windows.go new file mode 100644 index 000000000..ef5b625fc --- /dev/null +++ b/v3/pkg/application/dialogs_windows.go @@ -0,0 +1,266 @@ +//go:build windows + +package application + +import ( + "path/filepath" + "strings" + + "github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd" + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +func (m *windowsApp) showAboutDialog(title string, message string, _ []byte) { + about := newDialogImpl(&MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: InfoDialogType, + Title: title, + Message: message, + }, + }) + about.UseAppIcon = true + about.show() +} + +type windowsDialog struct { + dialog *MessageDialog + + //dialogImpl unsafe.Pointer + UseAppIcon bool +} + +func (m *windowsDialog) show() { + + title := w32.MustStringToUTF16Ptr(m.dialog.Title) + message := w32.MustStringToUTF16Ptr(m.dialog.Message) + flags := calculateMessageDialogFlags(m.dialog.MessageDialogOptions) + var button int32 + + var parentWindow uintptr + var err error + if m.dialog.window != nil { + parentWindow, err = m.dialog.window.NativeWindowHandle() + if err != nil { + globalApplication.handleFatalError(err) + } + } + + if m.UseAppIcon || m.dialog.Icon != nil { + // 3 is the application icon + button, err = w32.MessageBoxWithIcon(parentWindow, message, title, 3, windows.MB_OK|windows.MB_USERICON) + if err != nil { + globalApplication.handleFatalError(err) + } + } else { + button, err = windows.MessageBox(windows.HWND(parentWindow), message, title, flags|windows.MB_SYSTEMMODAL) + if err != nil { + globalApplication.handleFatalError(err) + } + } + // This maps MessageBox return values to strings + responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"} + result := "Error" + if int(button) < len(responses) { + result = responses[button] + } + // Check if there's a callback for the button pressed + for _, buttonInDialog := range m.dialog.Buttons { + if buttonInDialog.Label == result { + if buttonInDialog.Callback != nil { + buttonInDialog.Callback() + } + } + } +} + +func newDialogImpl(d *MessageDialog) *windowsDialog { + return &windowsDialog{ + dialog: d, + } +} + +type windowOpenFileDialog struct { + dialog *OpenFileDialogStruct +} + +func newOpenFileDialogImpl(d *OpenFileDialogStruct) *windowOpenFileDialog { + return &windowOpenFileDialog{ + dialog: d, + } +} + +func getDefaultFolder(folder string) (string, error) { + if folder == "" { + return "", nil + } + return filepath.Abs(folder) +} + +func (m *windowOpenFileDialog) show() (chan string, error) { + + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + return nil, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "PickFolder", + FileFilters: convertFilters(m.dialog.filters), + Folder: defaultFolder, + } + + if m.dialog.window != nil { + config.ParentWindowHandle, err = m.dialog.window.NativeWindowHandle() + if err != nil { + globalApplication.handleFatalError(err) + } + } + + var result []string + if m.dialog.allowsMultipleSelection && !m.dialog.canChooseDirectories { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenMultipleFilesDialog(config) + }, true) + if err != nil { + return nil, err + } + result = temp.([]string) + } else { + if m.dialog.canChooseDirectories { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewSelectFolderDialog(config) + }, false) + if err != nil { + return nil, err + } + result = []string{temp.(string)} + } else { + temp, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewOpenFileDialog(config) + }, false) + if err != nil { + return nil, err + } + result = []string{temp.(string)} + } + } + + files := make(chan string) + go func() { + defer handlePanic() + for _, file := range result { + files <- file + } + close(files) + }() + return files, nil +} + +type windowSaveFileDialog struct { + dialog *SaveFileDialogStruct +} + +func newSaveFileDialogImpl(d *SaveFileDialogStruct) *windowSaveFileDialog { + return &windowSaveFileDialog{ + dialog: d, + } +} + +func (m *windowSaveFileDialog) show() (chan string, error) { + files := make(chan string) + defaultFolder, err := getDefaultFolder(m.dialog.directory) + if err != nil { + close(files) + return files, err + } + + config := cfd.DialogConfig{ + Title: m.dialog.title, + Role: "SaveFile", + FileFilters: convertFilters(m.dialog.filters), + FileName: m.dialog.filename, + Folder: defaultFolder, + } + + // Original PR for v2 by @almas1992: https://github.com/wailsapp/wails/pull/3205 + if len(m.dialog.filters) > 0 { + config.DefaultExtension = strings.TrimPrefix(strings.Split(m.dialog.filters[0].Pattern, ";")[0], "*") + } + + result, err := showCfdDialog( + func() (cfd.Dialog, error) { + return cfd.NewSaveFileDialog(config) + }, false) + go func() { + defer handlePanic() + files <- result.(string) + close(files) + }() + return files, err +} + +func calculateMessageDialogFlags(options MessageDialogOptions) uint32 { + var flags uint32 + + switch options.DialogType { + case InfoDialogType: + flags = windows.MB_OK | windows.MB_ICONINFORMATION + case ErrorDialogType: + flags = windows.MB_ICONERROR | windows.MB_OK + case QuestionDialogType: + flags = windows.MB_YESNO + for _, button := range options.Buttons { + if strings.TrimSpace(strings.ToLower(button.Label)) == "no" && button.IsDefault { + flags |= windows.MB_DEFBUTTON2 + } + } + case WarningDialogType: + flags = windows.MB_OK | windows.MB_ICONWARNING + } + + return flags +} + +func convertFilters(filters []FileFilter) []cfd.FileFilter { + var result []cfd.FileFilter + for _, filter := range filters { + result = append(result, cfd.FileFilter(filter)) + } + return result +} + +func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) { + dlg, err := newDlg() + if err != nil { + return nil, err + } + defer func() { + err := dlg.Release() + if err != nil { + globalApplication.error("unable to release dialog: %w", err) + } + }() + + if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect { + paths, err := multi.ShowAndGetResults() + if err != nil { + return nil, err + } + + for i, path := range paths { + paths[i] = filepath.Clean(path) + } + return paths, nil + } + + path, err := dlg.ShowAndGetResult() + if err != nil { + return nil, err + } + return filepath.Clean(path), nil +} diff --git a/v3/pkg/application/dialogs_windows_test.go b/v3/pkg/application/dialogs_windows_test.go new file mode 100644 index 000000000..af4dabf75 --- /dev/null +++ b/v3/pkg/application/dialogs_windows_test.go @@ -0,0 +1,57 @@ +//go:build windows + +package application_test + +import ( + "path/filepath" + "testing" + + "github.com/matryer/is" +) + +func TestCleanPath(t *testing.T) { + i := is.New(t) + tests := []struct { + name string + inputPath string + expected string + }{ + { + name: "path with double separators", + inputPath: `C:\\temp\\folder`, + expected: `C:\temp\folder`, + }, + { + name: "path with forward slashes", + inputPath: `C://temp//folder`, + expected: `C:\temp\folder`, + }, + { + name: "path with trailing separator", + inputPath: `C:\\temp\\folder\\`, + expected: `C:\temp\folder`, + }, + { + name: "path with escaped tab character", + inputPath: `C:\\Users\\test\\tab.txt`, + expected: `C:\Users\test\tab.txt`, + }, + { + name: "newline character", + inputPath: `C:\\Users\\test\\newline\\n.txt`, + expected: `C:\Users\test\newline\n.txt`, + }, + { + name: "UNC path with multiple separators", + inputPath: `\\\\\\\\host\\share\\test.txt`, + expected: `\\\\host\share\test.txt`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cleaned := filepath.Clean(tt.inputPath) + i.Equal(cleaned, tt.expected) + }) + } +} diff --git a/v3/pkg/application/environment.go b/v3/pkg/application/environment.go new file mode 100644 index 000000000..68be3ba06 --- /dev/null +++ b/v3/pkg/application/environment.go @@ -0,0 +1,18 @@ +package application + +import "github.com/wailsapp/wails/v3/internal/operatingsystem" + +// EnvironmentInfo represents information about the current environment. +// +// Fields: +// - OS: the operating system that the program is running on. +// - Arch: the architecture of the operating system. +// - Debug: indicates whether debug mode is enabled. +// - OSInfo: information about the operating system. +type EnvironmentInfo struct { + OS string + Arch string + Debug bool + OSInfo *operatingsystem.OS + PlatformInfo map[string]any +} diff --git a/v3/pkg/application/environment_manager.go b/v3/pkg/application/environment_manager.go new file mode 100644 index 000000000..d32b6dd89 --- /dev/null +++ b/v3/pkg/application/environment_manager.go @@ -0,0 +1,56 @@ +package application + +import ( + "runtime" + + "github.com/wailsapp/wails/v3/internal/fileexplorer" + "github.com/wailsapp/wails/v3/internal/operatingsystem" +) + +// EnvironmentManager manages environment-related operations +type EnvironmentManager struct { + app *App +} + +// newEnvironmentManager creates a new EnvironmentManager instance +func newEnvironmentManager(app *App) *EnvironmentManager { + return &EnvironmentManager{ + app: app, + } +} + +// Info returns environment information +func (em *EnvironmentManager) Info() EnvironmentInfo { + info, _ := operatingsystem.Info() + result := EnvironmentInfo{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + Debug: em.app.isDebugMode, + OSInfo: info, + } + result.PlatformInfo = em.app.platformEnvironment() + return result +} + +// IsDarkMode returns true if the system is in dark mode +func (em *EnvironmentManager) IsDarkMode() bool { + if em.app.impl == nil { + return false + } + return em.app.impl.isDarkMode() +} + +// GetAccentColor returns the system accent color +func (em *EnvironmentManager) GetAccentColor() string { + if em.app.impl == nil { + return "rgb(0,122,255)" + } + return em.app.impl.getAccentColor() +} + +// OpenFileManager opens the file manager at the specified path, optionally selecting the file +func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error { + return InvokeSyncWithError(func() error { + return fileexplorer.OpenFileManager(path, selectFile) + }) +} diff --git a/v3/pkg/application/errors.go b/v3/pkg/application/errors.go new file mode 100644 index 000000000..d0b7edd6b --- /dev/null +++ b/v3/pkg/application/errors.go @@ -0,0 +1,55 @@ +package application + +import ( + "fmt" + "os" + "strings" +) + +// FatalError instances are passed to the registered error handler +// in case of catastrophic, unrecoverable failures that require immediate termination. +// FatalError wraps the original error value in an informative message. +// The underlying error may be retrieved through the [FatalError.Unwrap] method. +type FatalError struct { + err error + internal bool +} + +// Internal returns true when the error was triggered from wails' internal code. +func (e *FatalError) Internal() bool { + return e.internal +} + +// Unwrap returns the original cause of the fatal error, +// for easy inspection using the [errors.As] API. +func (e *FatalError) Unwrap() error { + return e.err +} + +func (e *FatalError) Error() string { + var buffer strings.Builder + buffer.WriteString("\n\n******************************** FATAL *********************************\n") + buffer.WriteString("* There has been a catastrophic failure in your application. *\n") + if e.internal { + buffer.WriteString("* Please report this error at https://github.com/wailsapp/wails/issues *\n") + } + buffer.WriteString("**************************** Error Details *****************************\n") + buffer.WriteString(e.err.Error()) + buffer.WriteString("************************************************************************\n") + return buffer.String() +} + +func Fatal(message string, args ...any) { + err := &FatalError{ + err: fmt.Errorf(message, args...), + internal: true, + } + + if globalApplication != nil { + globalApplication.handleError(err) + } else { + fmt.Println(err) + } + + os.Exit(1) +} diff --git a/v3/pkg/application/event_manager.go b/v3/pkg/application/event_manager.go new file mode 100644 index 000000000..1ee191e10 --- /dev/null +++ b/v3/pkg/application/event_manager.go @@ -0,0 +1,155 @@ +package application + +import ( + "slices" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// EventManager manages event-related operations +type EventManager struct { + app *App +} + +// newEventManager creates a new EventManager instance +func newEventManager(app *App) *EventManager { + return &EventManager{ + app: app, + } +} + +// Emit emits a custom event +func (em *EventManager) Emit(name string, data ...any) { + em.app.customEventProcessor.Emit(&CustomEvent{ + Name: name, + Data: data, + }) +} + +// EmitEvent emits a custom event object (internal use) +func (em *EventManager) EmitEvent(event *CustomEvent) { + em.app.customEventProcessor.Emit(event) +} + +// On registers a listener for custom events +func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() { + return em.app.customEventProcessor.On(name, callback) +} + +// Off removes all listeners for a custom event +func (em *EventManager) Off(name string) { + em.app.customEventProcessor.Off(name) +} + +// OnMultiple registers a listener for custom events that will be called N times +func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) { + em.app.customEventProcessor.OnMultiple(name, callback, counter) +} + +// Reset removes all custom event listeners +func (em *EventManager) Reset() { + em.app.customEventProcessor.OffAll() +} + +// OnApplicationEvent registers a listener for application events +func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() { + eventID := uint(eventType) + em.app.applicationEventListenersLock.Lock() + defer em.app.applicationEventListenersLock.Unlock() + listener := &EventListener{ + callback: callback, + } + em.app.applicationEventListeners[eventID] = append(em.app.applicationEventListeners[eventID], listener) + if em.app.impl != nil { + go func() { + defer handlePanic() + em.app.impl.on(eventID) + }() + } + + return func() { + // lock the map + em.app.applicationEventListenersLock.Lock() + defer em.app.applicationEventListenersLock.Unlock() + // Remove listener + em.app.applicationEventListeners[eventID] = lo.Without(em.app.applicationEventListeners[eventID], listener) + } +} + +// RegisterApplicationEventHook registers an application event hook +func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() { + eventID := uint(eventType) + em.app.applicationEventHooksLock.Lock() + defer em.app.applicationEventHooksLock.Unlock() + thisHook := &eventHook{ + callback: callback, + } + em.app.applicationEventHooks[eventID] = append(em.app.applicationEventHooks[eventID], thisHook) + + return func() { + em.app.applicationEventHooksLock.Lock() + em.app.applicationEventHooks[eventID] = lo.Without(em.app.applicationEventHooks[eventID], thisHook) + em.app.applicationEventHooksLock.Unlock() + } +} + +// Dispatch dispatches an event to listeners (internal use) +func (em *EventManager) dispatch(event *CustomEvent) { + // Snapshot windows under RLock + em.app.windowsLock.RLock() + for _, window := range em.app.windows { + if event.IsCancelled() { + em.app.windowsLock.RUnlock() + return + } + window.DispatchWailsEvent(event) + } + em.app.windowsLock.RUnlock() + + // Snapshot listeners under Lock + em.app.wailsEventListenerLock.Lock() + listeners := slices.Clone(em.app.wailsEventListeners) + em.app.wailsEventListenerLock.Unlock() + + for _, listener := range listeners { + if event.IsCancelled() { + return + } + listener.DispatchWailsEvent(event) + } +} + +// HandleApplicationEvent handles application events (internal use) +func (em *EventManager) handleApplicationEvent(event *ApplicationEvent) { + defer handlePanic() + em.app.applicationEventListenersLock.RLock() + listeners, ok := em.app.applicationEventListeners[event.Id] + em.app.applicationEventListenersLock.RUnlock() + if !ok { + return + } + + // Process Hooks + em.app.applicationEventHooksLock.RLock() + hooks, ok := em.app.applicationEventHooks[event.Id] + em.app.applicationEventHooksLock.RUnlock() + if ok { + for _, thisHook := range hooks { + thisHook.callback(event) + if event.IsCancelled() { + return + } + } + } + + for _, listener := range listeners { + go func() { + if event.IsCancelled() { + return + } + defer handlePanic() + listener.callback(event) + }() + } +} diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go new file mode 100644 index 000000000..973c3015c --- /dev/null +++ b/v3/pkg/application/events.go @@ -0,0 +1,266 @@ +package application + +import ( + "encoding/json" + "sync" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type ApplicationEvent struct { + Id uint + ctx *ApplicationEventContext + cancelled bool + lock sync.RWMutex +} + +func (w *ApplicationEvent) Context() *ApplicationEventContext { + return w.ctx +} + +func newApplicationEvent(id events.ApplicationEventType) *ApplicationEvent { + return &ApplicationEvent{ + Id: uint(id), + ctx: newApplicationEventContext(), + } +} + +func (w *ApplicationEvent) Cancel() { + w.lock.Lock() + defer w.lock.Unlock() + w.cancelled = true +} + +func (w *ApplicationEvent) IsCancelled() bool { + w.lock.RLock() + defer w.lock.RUnlock() + return w.cancelled +} + +var applicationEvents = make(chan *ApplicationEvent, 5) + +type windowEvent struct { + WindowID uint + EventID uint +} + +var windowEvents = make(chan *windowEvent, 5) + +var menuItemClicked = make(chan uint, 5) + +type CustomEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender"` // Name of the window sending the event, or "" if sent from application + cancelled bool + lock sync.RWMutex +} + +func (e *CustomEvent) Cancel() { + e.lock.Lock() + defer e.lock.Unlock() + e.cancelled = true +} + +func (e *CustomEvent) IsCancelled() bool { + e.lock.Lock() + defer e.lock.Unlock() + return e.cancelled +} + +func (e *CustomEvent) ToJSON() string { + marshal, err := json.Marshal(&e) + if err != nil { + // TODO: Fatal error? log? + return "" + } + return string(marshal) +} + +// WailsEventListener is an interface that can be implemented to listen for Wails events +// It is used by the RegisterListener method of the Application. +type WailsEventListener interface { + DispatchWailsEvent(event *CustomEvent) +} + +type hook struct { + callback func(*CustomEvent) +} + +// eventListener holds a callback function which is invoked when +// the event listened for is emitted. It has a counter which indicates +// how the total number of events it is interested in. A value of zero +// means it does not expire (default). +type eventListener struct { + callback func(*CustomEvent) // Function to call with emitted event data + counter int // The number of times this callback may be called. -1 = infinite + delete bool // Flag to indicate that this listener should be deleted +} + +// EventProcessor handles custom events +type EventProcessor struct { + // Go event listeners + listeners map[string][]*eventListener + notifyLock sync.RWMutex + dispatchEventToWindows func(*CustomEvent) + hooks map[string][]*hook + hookLock sync.RWMutex +} + +func NewWailsEventProcessor(dispatchEventToWindows func(*CustomEvent)) *EventProcessor { + return &EventProcessor{ + listeners: make(map[string][]*eventListener), + dispatchEventToWindows: dispatchEventToWindows, + hooks: make(map[string][]*hook), + } +} + +// On is the equivalent of Javascript's `addEventListener` +func (e *EventProcessor) On(eventName string, callback func(event *CustomEvent)) func() { + return e.registerListener(eventName, callback, -1) +} + +// OnMultiple is the same as `OnApplicationEvent` but will unregister after `count` events +func (e *EventProcessor) OnMultiple(eventName string, callback func(event *CustomEvent), counter int) func() { + return e.registerListener(eventName, callback, counter) +} + +// Once is the same as `OnApplicationEvent` but will unregister after the first event +func (e *EventProcessor) Once(eventName string, callback func(event *CustomEvent)) func() { + return e.registerListener(eventName, callback, 1) +} + +// Emit sends an event to all listeners +func (e *EventProcessor) Emit(thisEvent *CustomEvent) { + if thisEvent == nil { + return + } + + // If we have any hooks, run them first and check if the event was cancelled + if e.hooks != nil { + if hooks, ok := e.hooks[thisEvent.Name]; ok { + for _, thisHook := range hooks { + thisHook.callback(thisEvent) + if thisEvent.IsCancelled() { + return + } + } + } + } + + go func() { + defer handlePanic() + e.dispatchEventToListeners(thisEvent) + }() + go func() { + defer handlePanic() + e.dispatchEventToWindows(thisEvent) + }() +} + +func (e *EventProcessor) Off(eventName string) { + e.unRegisterListener(eventName) +} + +func (e *EventProcessor) OffAll() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + e.listeners = make(map[string][]*eventListener) +} + +// registerListener provides a means of subscribing to events of type "eventName" +func (e *EventProcessor) registerListener(eventName string, callback func(*CustomEvent), counter int) func() { + // Create new eventListener + thisListener := &eventListener{ + callback: callback, + counter: counter, + delete: false, + } + e.notifyLock.Lock() + // Append the new listener to the listeners slice + e.listeners[eventName] = append(e.listeners[eventName], thisListener) + e.notifyLock.Unlock() + return func() { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + if _, ok := e.listeners[eventName]; !ok { + return + } + e.listeners[eventName] = lo.Filter(e.listeners[eventName], func(l *eventListener, i int) bool { + return l != thisListener + }) + } +} + +// RegisterHook provides a means of registering methods to be called before emitting the event +func (e *EventProcessor) RegisterHook(eventName string, callback func(*CustomEvent)) func() { + // Create new hook + thisHook := &hook{ + callback: callback, + } + e.hookLock.Lock() + // Append the new listener to the listeners slice + e.hooks[eventName] = append(e.hooks[eventName], thisHook) + e.hookLock.Unlock() + return func() { + e.hookLock.Lock() + defer e.hookLock.Unlock() + + if _, ok := e.hooks[eventName]; !ok { + return + } + e.hooks[eventName] = lo.Filter(e.hooks[eventName], func(l *hook, i int) bool { + return l != thisHook + }) + } +} + +// unRegisterListener provides a means of unsubscribing to events of type "eventName" +func (e *EventProcessor) unRegisterListener(eventName string) { + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + delete(e.listeners, eventName) +} + +// dispatchEventToListeners calls all registered listeners event name +func (e *EventProcessor) dispatchEventToListeners(event *CustomEvent) { + + e.notifyLock.Lock() + defer e.notifyLock.Unlock() + + listeners := e.listeners[event.Name] + if listeners == nil { + return + } + + // We have a dirty flag to indicate that there are items to delete + itemsToDelete := false + + // Callback in goroutine + for _, listener := range listeners { + if listener.counter > 0 { + listener.counter-- + } + go func() { + if event.IsCancelled() { + return + } + defer handlePanic() + listener.callback(event) + }() + + if listener.counter == 0 { + listener.delete = true + itemsToDelete = true + } + } + + // Do we have items to delete? + if itemsToDelete == true { + e.listeners[event.Name] = lo.Filter(listeners, func(l *eventListener, i int) bool { + return l.delete == false + }) + } +} diff --git a/v3/pkg/application/events_common_darwin.go b/v3/pkg/application/events_common_darwin.go new file mode 100644 index 000000000..6fce7fcd4 --- /dev/null +++ b/v3/pkg/application/events_common_darwin.go @@ -0,0 +1,21 @@ +//go:build darwin + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Mac.ApplicationDidFinishLaunching: events.Common.ApplicationStarted, + events.Mac.ApplicationDidChangeTheme: events.Common.ThemeChanged, +} + +func (m *macosApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_common_linux.go b/v3/pkg/application/events_common_linux.go new file mode 100644 index 000000000..d16232648 --- /dev/null +++ b/v3/pkg/application/events_common_linux.go @@ -0,0 +1,21 @@ +//go:build linux + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Linux.ApplicationStartup: events.Common.ApplicationStarted, + events.Linux.SystemThemeChanged: events.Common.ThemeChanged, +} + +func (a *linuxApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + a.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_common_windows.go b/v3/pkg/application/events_common_windows.go new file mode 100644 index 000000000..1c4dd55e6 --- /dev/null +++ b/v3/pkg/application/events_common_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +var commonApplicationEventMap = map[events.ApplicationEventType]events.ApplicationEventType{ + events.Windows.SystemThemeChanged: events.Common.ThemeChanged, + events.Windows.ApplicationStarted: events.Common.ApplicationStarted, +} + +func (m *windowsApp) setupCommonEvents() { + for sourceEvent, targetEvent := range commonApplicationEventMap { + sourceEvent := sourceEvent + targetEvent := targetEvent + m.parent.Event.OnApplicationEvent(sourceEvent, func(event *ApplicationEvent) { + event.Id = uint(targetEvent) + applicationEvents <- event + }) + } +} diff --git a/v3/pkg/application/events_test.go b/v3/pkg/application/events_test.go new file mode 100644 index 000000000..ee1c26b2f --- /dev/null +++ b/v3/pkg/application/events_test.go @@ -0,0 +1,135 @@ +package application_test + +import ( + "sync" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + + "github.com/matryer/is" +) + +type mockNotifier struct { + Events []*application.CustomEvent +} + +func (m *mockNotifier) dispatchEventToWindows(event *application.CustomEvent) { + m.Events = append(m.Events, event) +} + +func (m *mockNotifier) Reset() { + m.Events = []*application.CustomEvent{} +} + +func Test_EventsOn(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.On(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} + +func Test_EventsOnce(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.Once(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} +func Test_EventsOnMultiple(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test OnApplicationEvent + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(2) + unregisterFn := eventProcessor.OnMultiple(eventName, func(event *application.CustomEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }, 2) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(2, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.CustomEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} diff --git a/v3/pkg/application/image.go b/v3/pkg/application/image.go new file mode 100644 index 000000000..81c519739 --- /dev/null +++ b/v3/pkg/application/image.go @@ -0,0 +1,37 @@ +package application + +import ( + "bytes" + "image" + "image/draw" + "image/png" +) + +func pngToImage(data []byte) (*image.RGBA, error) { + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, bounds.Min, draw.Src) + return rgba, nil +} + +func ToARGB(img *image.RGBA) (int, int, []byte) { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + data := make([]byte, w*h*4) + i := 0 + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + r, g, b, a := img.At(x, y).RGBA() + data[i] = byte(a) + data[i+1] = byte(r) + data[i+2] = byte(g) + data[i+3] = byte(b) + i += 4 + } + } + return w, h, data +} diff --git a/v3/pkg/application/internal/tests/services/common.go b/v3/pkg/application/internal/tests/services/common.go new file mode 100644 index 000000000..9d35da69e --- /dev/null +++ b/v3/pkg/application/internal/tests/services/common.go @@ -0,0 +1,168 @@ +package services + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Config struct { + Id int + T *testing.T + Seq *atomic.Int64 + Options application.ServiceOptions + StartupErr bool + ShutdownErr bool +} + +func Configure[T any, P interface { + *T + Configure(Config) +}](srv P, c Config) application.Service { + srv.Configure(c) + return application.NewServiceWithOptions(srv, c.Options) +} + +type Error struct { + Id int +} + +func (e *Error) Error() string { + return fmt.Sprintf("service #%d mock failure", e.Id) +} + +type Startupper struct { + Config + startup int64 +} + +func (s *Startupper) Configure(c Config) { + s.Config = c +} + +func (s *Startupper) Id() int { + return s.Config.Id +} + +func (s *Startupper) StartupSeq() int64 { + return s.startup +} + +func (s *Startupper) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if s.startup != 0 { + s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load()) + return nil + } + + s.startup = s.Seq.Add(1) + + if diff := cmp.Diff(s.Options, options); diff != "" { + s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff) + } + + if s.StartupErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +type Shutdowner struct { + Config + shutdown int64 +} + +func (s *Shutdowner) Configure(c Config) { + s.Config = c +} + +func (s *Shutdowner) Id() int { + return s.Config.Id +} + +func (s *Shutdowner) ShutdownSeq() int64 { + return s.shutdown +} + +func (s *Shutdowner) ServiceShutdown() error { + if s.shutdown != 0 { + s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load()) + return nil + } + + s.shutdown = s.Seq.Add(1) + + if s.ShutdownErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +type StartupShutdowner struct { + Config + startup int64 + shutdown int64 + ctx context.Context +} + +func (s *StartupShutdowner) Configure(c Config) { + s.Config = c +} + +func (s *StartupShutdowner) Id() int { + return s.Config.Id +} + +func (s *StartupShutdowner) StartupSeq() int64 { + return s.startup +} + +func (s *StartupShutdowner) ShutdownSeq() int64 { + return s.shutdown +} + +func (s *StartupShutdowner) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if s.startup != 0 { + s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load()) + return nil + } + + s.startup = s.Seq.Add(1) + s.ctx = ctx + + if diff := cmp.Diff(s.Options, options); diff != "" { + s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff) + } + + if s.StartupErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} + +func (s *StartupShutdowner) ServiceShutdown() error { + if s.shutdown != 0 { + s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load()) + return nil + } + + s.shutdown = s.Seq.Add(1) + + select { + case <-s.ctx.Done(): + default: + s.T.Errorf("Service #%d shut down before context cancellation", s.Id()) + } + + if s.ShutdownErr { + return &Error{Id: s.Id()} + } else { + return nil + } +} diff --git a/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go b/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go new file mode 100644 index 000000000..ad8df141c --- /dev/null +++ b/v3/pkg/application/internal/tests/services/shutdown/shutdown_test.go @@ -0,0 +1,92 @@ +package shutdown + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Shutdowner } + Service2 struct{ svctest.Shutdowner } + Service3 struct{ svctest.Shutdowner } + Service4 struct{ svctest.Shutdowner } + Service5 struct{ svctest.Shutdowner } + Service6 struct{ svctest.Shutdowner } +) + +func TestServiceShutdown(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 5) + validate(t, services[1], 4) + validate(t, services[2], 2, 3) + validate(t, services[3], 1) + validate(t, services[4], 1) + validate(t, services[5], 0) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if seq == 0 { + t.Errorf("Service #%d did not shut down", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go b/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go new file mode 100644 index 000000000..42949b037 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/shutdownerror/shutdownerror_test.go @@ -0,0 +1,123 @@ +package shutdownerror + +import ( + "errors" + "slices" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Shutdowner } + Service2 struct{ svctest.Shutdowner } + Service3 struct{ svctest.Shutdowner } + Service4 struct{ svctest.Shutdowner } + Service5 struct{ svctest.Shutdowner } + Service6 struct{ svctest.Shutdowner } +) + +func TestServiceShutdownError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, ShutdownErr: true}), + } + + expectedShutdownErrors := []int{5, 4, 1} + var errCount atomic.Int64 + + var app *application.App + app = apptest.New(t, application.Options{ + Services: services[:3], + ErrorHandler: func(err error) { + var mock *svctest.Error + if !errors.As(err, &mock) { + app.Logger.Error(err.Error()) + return + } + + i := int(errCount.Add(1) - 1) + if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] { + return + } + + cut := min(i, len(expectedShutdownErrors)) + if slices.Contains(expectedShutdownErrors[:cut], mock.Id) { + t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id) + } else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) { + t.Errorf("Early shutdown error for service #%d", mock.Id) + } else { + t.Errorf("Unexpected shutdown error for service #%d", mock.Id) + } + }, + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) { + t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 5) + validate(t, services[1], 4) + validate(t, services[2], 2, 3) + validate(t, services[3], 1) + validate(t, services[4], 1) + validate(t, services[5], 0) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if seq == 0 { + t.Errorf("Service #%d did not shut down", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startup/startup_test.go b/v3/pkg/application/internal/tests/services/startup/startup_test.go new file mode 100644 index 000000000..bf5c2ac96 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startup/startup_test.go @@ -0,0 +1,102 @@ +package startup + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Startupper } + Service2 struct{ svctest.Startupper } + Service3 struct{ svctest.Startupper } + Service4 struct{ svctest.Startupper } + Service5 struct{ svctest.Startupper } + Service6 struct{ svctest.Startupper } +) + +func TestServiceStartup(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 2", + }}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq, Options: application.ServiceOptions{ + Route: "/mounted/here", + }}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 5", + Route: "/mounted/there", + }}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, Options: application.ServiceOptions{ + Name: "I am service 6", + Route: "/mounted/elsewhere", + }}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count) + } + + validate(t, services[0], 0) + validate(t, services[1], 1) + validate(t, services[2], 2) + validate(t, services[3], 3) + validate(t, services[4], 3) + validate(t, services[5], 4, 5) +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + + if seq == 0 { + t.Errorf("Service #%d did not start up", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go b/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go new file mode 100644 index 000000000..a4a3c5bc4 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startuperror/startuperror_test.go @@ -0,0 +1,114 @@ +package startuperror + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.Startupper } + Service2 struct{ svctest.Startupper } + Service3 struct{ svctest.Startupper } + Service4 struct{ svctest.Startupper } + Service5 struct{ svctest.Startupper } + Service6 struct{ svctest.Startupper } +) + +func TestServiceStartupError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + t.Errorf("Application started") + app.Quit() + }) + + var mock *svctest.Error + + err := apptest.Run(t, app) + if err != nil { + if !errors.As(err, &mock) { + t.Fatal(err) + } + } + + if mock == nil { + t.Fatal("Wanted startup error for service #3 or #4, got none") + } else if mock.Id != 3 && mock.Id != 4 { + t.Errorf("Wanted startup error for service #3 or #4, got #%d", mock.Id) + } + + if count := seq.Load(); count != 4 { + t.Errorf("Wrong startup call count: wanted %d, got %d", 4, count) + } + + validate(t, services[0], 0) + validate(t, services[1], 1) + validate(t, services[2], 2) + validate(t, services[mock.Id], 3) + + notStarted := 3 + if mock.Id == 3 { + notStarted = 4 + } + + if seq := services[notStarted].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 { + t.Errorf("Service #%d started up unexpectedly at seq=%d", notStarted, seq) + } + if seq := services[5].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 { + t.Errorf("Service #5 started up unexpectedly at seq=%d", seq) + } +} + +func validate(t *testing.T, svc application.Service, prev ...int64) { + id := svc.Instance().(interface{ Id() int }).Id() + seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + + if seq == 0 { + t.Errorf("Service #%d did not start up", id) + return + } + + for _, p := range prev { + if seq <= p { + t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq) + } + } +} diff --git a/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go b/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go new file mode 100644 index 000000000..5f8cfc365 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startupshutdown/startupshutdown_test.go @@ -0,0 +1,102 @@ +package startupshutdown + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.StartupShutdowner } + Service2 struct{ svctest.StartupShutdowner } + Service3 struct{ svctest.StartupShutdowner } + Service4 struct{ svctest.StartupShutdowner } + Service5 struct{ svctest.StartupShutdowner } + Service6 struct{ svctest.StartupShutdowner } +) + +func TestServiceStartupShutdown(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}), + } + + app := apptest.New(t, application.Options{ + Services: services[:3], + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count) + } + seq.Store(0) + app.Quit() + }) + + err := apptest.Run(t, app) + if err != nil { + t.Fatal(err) + } + + if count := seq.Load(); count != int64(len(services)) { + t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count) + } + + bound := int64(len(services)) + 1 + validate(t, services[0], bound) + validate(t, services[1], bound) + validate(t, services[2], bound) + validate(t, services[3], bound) + validate(t, services[4], bound) + validate(t, services[5], bound) +} + +func validate(t *testing.T, svc application.Service, bound int64) { + id := svc.Instance().(interface{ Id() int }).Id() + startup := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + shutdown := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if startup == 0 && shutdown == 0 { + t.Errorf("Service #%d did not start nor shut down", id) + return + } else if startup == 0 { + t.Errorf("Service #%d started, but did not shut down", id) + return + } else if shutdown == 0 { + t.Errorf("Service #%d shut down, but did not start", id) + return + } + + if shutdown != bound-startup { + t.Errorf("Wrong sequence numbers for service #%d: wanted either %d..%d or %d..%d, got %d..%d", id, startup, bound-startup, bound-shutdown, shutdown, startup, shutdown) + } +} diff --git a/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go b/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go new file mode 100644 index 000000000..0ca135269 --- /dev/null +++ b/v3/pkg/application/internal/tests/services/startupshutdownerror/startupshutdownerror_test.go @@ -0,0 +1,140 @@ +package startupshutdownerror + +import ( + "errors" + "slices" + "sync" + "sync/atomic" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" + apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests" + svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func TestMain(m *testing.M) { + apptest.Main(m) +} + +type ( + Service1 struct{ svctest.StartupShutdowner } + Service2 struct{ svctest.StartupShutdowner } + Service3 struct{ svctest.StartupShutdowner } + Service4 struct{ svctest.StartupShutdowner } + Service5 struct{ svctest.StartupShutdowner } + Service6 struct{ svctest.StartupShutdowner } +) + +func TestServiceStartupShutdownError(t *testing.T) { + var seq atomic.Int64 + + services := []application.Service{ + svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}), + svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}), + svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}), + svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}), + } + + expectedShutdownErrors := []int{1} + var errCount atomic.Int64 + + var app *application.App + app = apptest.New(t, application.Options{ + Services: services[:3], + ErrorHandler: func(err error) { + var mock *svctest.Error + if !errors.As(err, &mock) { + app.Logger.Error(err.Error()) + return + } + + i := int(errCount.Add(1) - 1) + if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] { + return + } + + cut := min(i, len(expectedShutdownErrors)) + if slices.Contains(expectedShutdownErrors[:cut], mock.Id) { + t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id) + } else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) { + t.Errorf("Early shutdown error for service #%d", mock.Id) + } else { + t.Errorf("Unexpected shutdown error for service #%d", mock.Id) + } + }, + }) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + app.RegisterService(services[3]) + wg.Done() + }() + go func() { + app.RegisterService(services[4]) + wg.Done() + }() + wg.Wait() + + app.RegisterService(services[5]) + + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + t.Errorf("Application started") + app.Quit() + }) + + var mock *svctest.Error + + err := apptest.Run(t, app) + if err != nil { + if !errors.As(err, &mock) { + t.Fatal(err) + } + } + + if mock == nil { + t.Fatal("Wanted error for service #3 or #4, got none") + } else if mock.Id != 3 && mock.Id != 4 { + t.Errorf("Wanted error for service #3 or #4, got #%d", mock.Id) + } + + if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) { + t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec) + } + + if count := seq.Load(); count != 4+3 { + t.Errorf("Wrong startup+shutdown call count: wanted %d+%d, got %d", 4, 3, count) + } + + validate(t, services[0], true, true) + validate(t, services[1], true, true) + validate(t, services[2], true, true) + validate(t, services[3], mock.Id == 3, false) + validate(t, services[4], mock.Id == 4, false) + validate(t, services[5], false, false) +} + +func validate(t *testing.T, svc application.Service, startup bool, shutdown bool) { + id := svc.Instance().(interface{ Id() int }).Id() + startupSeq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq() + shutdownSeq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq() + + if startup != (startupSeq != 0) { + if startupSeq == 0 { + t.Errorf("Service #%d did not start up", id) + } else { + t.Errorf("Unexpected startup for service #%d at seq=%d", id, startupSeq) + } + } + + if shutdown != (shutdownSeq != 0) { + if shutdownSeq == 0 { + t.Errorf("Service #%d did not shut down", id) + } else { + t.Errorf("Unexpected shutdown for service #%d at seq=%d", id, shutdownSeq) + } + } +} diff --git a/v3/pkg/application/internal/tests/utils.go b/v3/pkg/application/internal/tests/utils.go new file mode 100644 index 000000000..ef94ef5ba --- /dev/null +++ b/v3/pkg/application/internal/tests/utils.go @@ -0,0 +1,74 @@ +package tests + +import ( + "errors" + "os" + "runtime" + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +var appChan chan *application.App = make(chan *application.App, 1) +var errChan chan error = make(chan error, 1) +var endChan chan error = make(chan error, 1) + +func init() { runtime.LockOSThread() } + +func New(t *testing.T, options application.Options) *application.App { + var app *application.App + + app = application.Get() + if app != nil { + return app + } + + if options.Name == "" { + options.Name = t.Name() + } + + errorHandler := options.ErrorHandler + options.ErrorHandler = func(err error) { + if fatal := (*application.FatalError)(nil); errors.As(err, &fatal) { + endChan <- err + select {} // Block forever + } else if errorHandler != nil { + errorHandler(err) + } else { + app.Logger.Error(err.Error()) + } + } + + postShutdown := options.PostShutdown + options.PostShutdown = func() { + if postShutdown != nil { + postShutdown() + } + endChan <- nil + select {} // Block forever + } + + return application.New(options) +} + +func Run(t *testing.T, app *application.App) error { + appChan <- app + select { + case err := <-errChan: + return err + case fatal := <-endChan: + if fatal != nil { + t.Fatal(fatal) + } + return fatal + } +} + +func Main(m *testing.M) { + go func() { + os.Exit(m.Run()) + }() + + errChan <- (<-appChan).Run() + select {} // Block forever +} diff --git a/v3/pkg/application/key_binding_manager.go b/v3/pkg/application/key_binding_manager.go new file mode 100644 index 000000000..490cc1cd3 --- /dev/null +++ b/v3/pkg/application/key_binding_manager.go @@ -0,0 +1,66 @@ +package application + +// KeyBindingManager manages all key binding operations +type KeyBindingManager struct { + app *App +} + +// newKeyBindingManager creates a new KeyBindingManager instance +func newKeyBindingManager(app *App) *KeyBindingManager { + return &KeyBindingManager{ + app: app, + } +} + +// Add adds a key binding +func (kbm *KeyBindingManager) Add(accelerator string, callback func(window *WebviewWindow)) { + kbm.app.keyBindingsLock.Lock() + defer kbm.app.keyBindingsLock.Unlock() + kbm.app.keyBindings[accelerator] = callback +} + +// Remove removes a key binding +func (kbm *KeyBindingManager) Remove(accelerator string) { + kbm.app.keyBindingsLock.Lock() + defer kbm.app.keyBindingsLock.Unlock() + delete(kbm.app.keyBindings, accelerator) +} + +// Process processes a key binding and returns true if handled +func (kbm *KeyBindingManager) Process(accelerator string, window *WebviewWindow) bool { + kbm.app.keyBindingsLock.RLock() + callback, exists := kbm.app.keyBindings[accelerator] + kbm.app.keyBindingsLock.RUnlock() + + if exists && callback != nil { + callback(window) + return true + } + return false +} + +// KeyBinding represents a key binding with its accelerator and callback +type KeyBinding struct { + Accelerator string + Callback func(window *WebviewWindow) +} + +// GetAll returns all registered key bindings as a slice +func (kbm *KeyBindingManager) GetAll() []*KeyBinding { + kbm.app.keyBindingsLock.RLock() + defer kbm.app.keyBindingsLock.RUnlock() + + result := make([]*KeyBinding, 0, len(kbm.app.keyBindings)) + for accelerator, callback := range kbm.app.keyBindings { + result = append(result, &KeyBinding{ + Accelerator: accelerator, + Callback: callback, + }) + } + return result +} + +// HandleWindowKeyEvent handles window key events (internal use) +func (kbm *KeyBindingManager) handleWindowKeyEvent(event *windowKeyEvent) { + kbm.app.handleWindowKeyEvent(event) +} diff --git a/v3/pkg/application/keys.go b/v3/pkg/application/keys.go new file mode 100644 index 000000000..375b76289 --- /dev/null +++ b/v3/pkg/application/keys.go @@ -0,0 +1,220 @@ +package application + +import ( + "fmt" + "runtime" + "slices" + "strconv" + "strings" +) + +// modifier is actually a string +type modifier int + +const ( + // CmdOrCtrlKey represents Command on Mac and Control on other platforms + CmdOrCtrlKey modifier = 0 << iota + // OptionOrAltKey represents Option on Mac and Alt on other platforms + OptionOrAltKey modifier = 1 << iota + // ShiftKey represents the shift key on all systems + ShiftKey modifier = 2 << iota + // SuperKey represents Command on Mac and the Windows key on the other platforms + SuperKey modifier = 3 << iota + // ControlKey represents the control key on all systems + ControlKey modifier = 4 << iota +) + +func (m modifier) String() string { + return modifierStringMap[runtime.GOOS][m] +} + +var modifierStringMap = map[string]map[modifier]string{ + "windows": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Win", + }, + "darwin": { + CmdOrCtrlKey: "Cmd", + ControlKey: "Ctrl", + OptionOrAltKey: "Option", + ShiftKey: "Shift", + SuperKey: "Cmd", + }, + "linux": { + CmdOrCtrlKey: "Ctrl", + ControlKey: "Ctrl", + OptionOrAltKey: "Alt", + ShiftKey: "Shift", + SuperKey: "Super", + }, +} + +var modifierMap = map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": ControlKey, + "optionoralt": OptionOrAltKey, + "alt": OptionOrAltKey, + "option": OptionOrAltKey, + "shift": ShiftKey, + "super": SuperKey, +} + +// accelerator holds the keyboard shortcut for a menu item +type accelerator struct { + Key string + Modifiers []modifier +} + +func (a *accelerator) clone() *accelerator { + result := *a + return &result +} + +func (a *accelerator) String() string { + var result []string + // Sort modifiers + for _, modifier := range a.Modifiers { + result = append(result, modifier.String()) + } + slices.Sort(result) + if len(a.Key) > 0 { + result = append(result, strings.ToUpper(a.Key)) + } + return strings.Join(result, "+") +} + +var namedKeys = map[string]struct{}{ + "backspace": {}, + "tab": {}, + "return": {}, + "enter": {}, + "escape": {}, + "left": {}, + "right": {}, + "up": {}, + "down": {}, + "space": {}, + "delete": {}, + "home": {}, + "end": {}, + "page up": {}, + "page down": {}, + "f1": {}, + "f2": {}, + "f3": {}, + "f4": {}, + "f5": {}, + "f6": {}, + "f7": {}, + "f8": {}, + "f9": {}, + "f10": {}, + "f11": {}, + "f12": {}, + "f13": {}, + "f14": {}, + "f15": {}, + "f16": {}, + "f17": {}, + "f18": {}, + "f19": {}, + "f20": {}, + "f21": {}, + "f22": {}, + "f23": {}, + "f24": {}, + "f25": {}, + "f26": {}, + "f27": {}, + "f28": {}, + "f29": {}, + "f30": {}, + "f31": {}, + "f32": {}, + "f33": {}, + "f34": {}, + "f35": {}, + "numlock": {}, +} + +func parseKey(key string) (string, bool) { + + // Lowercase! + key = strings.ToLower(key) + + // Check special case + if key == "plus" { + return "+", true + } + + // Handle named keys + _, namedKey := namedKeys[key] + if namedKey { + return key, true + } + + // Check we only have a single character + if len(key) != 1 { + return "", false + } + + runeKey := rune(key[0]) + + // This may be too inclusive + if strconv.IsPrint(runeKey) { + return key, true + } + + return "", false + +} + +// parseAccelerator parses a string into an accelerator +func parseAccelerator(shortcut string) (*accelerator, error) { + + var result accelerator + + // Split the shortcut by + + components := strings.Split(shortcut, "+") + + // If we only have one it should be a key + // We require components + if len(components) == 0 { + return nil, fmt.Errorf("no components given to validateComponents") + } + + modifiers := map[modifier]struct{}{} + + // Check components + for index, component := range components { + + // If last component + if index == len(components)-1 { + processedKey, validKey := parseKey(component) + if !validKey { + return nil, fmt.Errorf("'%s' is not a valid key", component) + } + result.Key = strings.ToLower(processedKey) + continue + } + + // Not last component - needs to be modifier + lowercaseComponent := strings.ToLower(component) + thisModifier, valid := modifierMap[lowercaseComponent] + if !valid { + return nil, fmt.Errorf("'%s' is not a valid modifier", component) + } + // Save this data + modifiers[thisModifier] = struct{}{} + } + // return the keys as a slice + for thisModifier := range modifiers { + result.Modifiers = append(result.Modifiers, thisModifier) + } + return &result, nil +} diff --git a/v3/pkg/application/keys_darwin.go b/v3/pkg/application/keys_darwin.go new file mode 100644 index 000000000..42e1c4686 --- /dev/null +++ b/v3/pkg/application/keys_darwin.go @@ -0,0 +1,28 @@ +//go:build darwin + +package application + +const ( + NSEventModifierFlagShift = 1 << 17 // Set if Shift key is pressed. + NSEventModifierFlagControl = 1 << 18 // Set if Control key is pressed. + NSEventModifierFlagOption = 1 << 19 // Set if Option or Alternate key is pressed. + NSEventModifierFlagCommand = 1 << 20 // Set if Command key is pressed. +) + +// macModifierMap maps accelerator modifiers to macOS modifiers. +var macModifierMap = map[modifier]int{ + CmdOrCtrlKey: NSEventModifierFlagCommand, + ControlKey: NSEventModifierFlagControl, + OptionOrAltKey: NSEventModifierFlagOption, + ShiftKey: NSEventModifierFlagShift, + SuperKey: NSEventModifierFlagCommand, +} + +// toMacModifier converts the accelerator to a macOS modifier. +func toMacModifier(modifiers []modifier) int { + result := 0 + for _, modifier := range modifiers { + result |= macModifierMap[modifier] + } + return result +} diff --git a/v3/pkg/application/keys_linux.go b/v3/pkg/application/keys_linux.go new file mode 100644 index 000000000..e7b44e380 --- /dev/null +++ b/v3/pkg/application/keys_linux.go @@ -0,0 +1,165 @@ +//go:build linux + +package application + +var VirtualKeyCodes = map[uint]string{ + 0xff08: "backspace", + 0xff09: "tab", + 0xff0a: "linefeed", + 0xff0b: "clear", + 0xff0d: "return", + 0xff13: "pause", + 0xff14: "scrolllock", + 0xff15: "sysreq", + 0xff1b: "escape", + 0xffff: "delete", + 0xff50: "home", + 0xff51: "left", + + 0xffe1: "lshift", + 0xffe2: "rshift", + 0xffe3: "lcontrol", + 0xffe4: "rcontrol", + 0xffeb: "lmeta", + 0xffec: "rmeta", + 0xffed: "lalt", + 0xffee: "ralt", + // Multi-Lang + 0xff21: "kanji", + 0xff22: "muhenkan", + 0xff24: "henkan", + 0xff25: "hiragana", + 0xff26: "katakana", + 0xff27: "hiragana/katakana", + 0xff28: "zenkaku", + 0xff29: "hankaku", + 0xff2a: "zenkaku/hankaku", + 0xff2b: "touroku", + 0xff2c: "massyo", + 0xff2d: "kana lock", + 0xff2e: "kana shift", + 0xff2f: "eisu shift", + 0xff30: "eisu toggle", + 0xff37: "kanji bangou", + + // Directions + 0xff52: "up", + 0xff53: "right", + 0xff54: "down", + 0xff55: "pageup", + 0xff56: "pagedown", + 0xff57: "end", + 0xff58: "begin", + + + // Alphabet + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4a: "j", + 0x4b: "k", + 0x4c: "l", + 0x4d: "m", + 0x4e: "n", + 0x4f: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5a: "z", + + 0x5b: "lbracket", + 0x5c: "backslash", + 0x5d: "rbracket", + 0x5e: "caret", + 0x5f: "underscore", + 0x60: "grave", + // Capital Alphabet + 0x61: "a", + 0x62: "b", + 0x63: "c", + 0x64: "d", + 0x65: "e", + 0x66: "f", + 0x67: "g", + 0x68: "h", + 0x69: "i", + 0x6a: "j", + 0x6b: "k", + 0x6c: "l", + 0x6d: "m", + 0x6e: "n", + 0x6f: "o", + 0x70: "p", + 0x71: "q", + 0x72: "r", + 0x73: "s", + 0x74: "t", + 0x75: "u", + 0x76: "v", + 0x77: "w", + 0x78: "x", + 0x79: "y", + 0x7a: "z", + 0x7b: "lbrace", + 0x7c: "pipe", + 0x7d: "rbrace", + 0x7e: "tilde", + 0xa1: "exclam", + 0xa2: "cent", + 0xa3: "sterling", + 0xa4: "currency", + 0xa5: "yen", + 0xa6: "brokenbar", + 0xa7: "section", + 0xa8: "diaeresis", + 0xa9: "copyright", + 0xaa: "ordfeminine", + 0xab: "guillemotleft", + 0xad: "hyphen", + 0xae: "registered", + 0xaf: "macron", + 0xb0: "degree", + 0xb1: "plusminus", + 0xb2: "twosuperior", + + // Function Keys + 0xffbe: "f1", + 0xffbf: "f2", + 0xffc0: "f3", + 0xffc1: "f4", + 0xffc2: "f5", + 0xffc3: "f6", + 0xffc4: "f7", + 0xffc5: "f8", + 0xffc6: "f9", + 0xffc7: "f10", + 0xffc8: "f11", + 0xffc9: "f12", + 0xffca: "f13", + 0xffcb: "f14", + 0xffcc: "f15", + 0xffcd: "f16", + 0xffce: "f17", + 0xffcf: "f18", + 0xffd0: "f19", + 0xffd1: "f20", + 0xffd2: "f21", + 0xffd3: "f22", + 0xffd4: "f23", + 0xffd5: "f24", + + +} diff --git a/v3/pkg/application/keys_windows.go b/v3/pkg/application/keys_windows.go new file mode 100644 index 000000000..8011a837d --- /dev/null +++ b/v3/pkg/application/keys_windows.go @@ -0,0 +1,230 @@ +//go:build windows + +package application + +var VirtualKeyCodes = map[uint]string{ + 0x01: "lbutton", + 0x02: "rbutton", + 0x03: "cancel", + 0x04: "mbutton", + 0x05: "xbutton1", + 0x06: "xbutton2", + 0x08: "back", + 0x09: "tab", + 0x0C: "clear", + 0x0D: "return", + 0x10: "shift", + 0x11: "ctrl", + 0x12: "menu", + 0x13: "pause", + 0x14: "capital", + 0x15: "kana", + 0x17: "junja", + 0x18: "final", + 0x19: "hanja", + 0x1B: "escape", + 0x1C: "convert", + 0x1D: "nonconvert", + 0x1E: "accept", + 0x1F: "modechange", + 0x20: "space", + 0x21: "prior", + 0x22: "next", + 0x23: "end", + 0x24: "home", + 0x25: "left", + 0x26: "up", + 0x27: "right", + 0x28: "down", + 0x29: "select", + 0x2A: "print", + 0x2B: "execute", + 0x2C: "snapshot", + 0x2D: "insert", + 0x2E: "delete", + 0x2F: "help", + 0x30: "0", + 0x31: "1", + 0x32: "2", + 0x33: "3", + 0x34: "4", + 0x35: "5", + 0x36: "6", + 0x37: "7", + 0x38: "8", + 0x39: "9", + 0x41: "a", + 0x42: "b", + 0x43: "c", + 0x44: "d", + 0x45: "e", + 0x46: "f", + 0x47: "g", + 0x48: "h", + 0x49: "i", + 0x4A: "j", + 0x4B: "k", + 0x4C: "l", + 0x4D: "m", + 0x4E: "n", + 0x4F: "o", + 0x50: "p", + 0x51: "q", + 0x52: "r", + 0x53: "s", + 0x54: "t", + 0x55: "u", + 0x56: "v", + 0x57: "w", + 0x58: "x", + 0x59: "y", + 0x5A: "z", + 0x5B: "lwin", + 0x5C: "rwin", + 0x5D: "apps", + 0x5F: "sleep", + 0x60: "numpad0", + 0x61: "numpad1", + 0x62: "numpad2", + 0x63: "numpad3", + 0x64: "numpad4", + 0x65: "numpad5", + 0x66: "numpad6", + 0x67: "numpad7", + 0x68: "numpad8", + 0x69: "numpad9", + 0x6A: "multiply", + 0x6B: "add", + 0x6C: "separator", + 0x6D: "subtract", + 0x6E: "decimal", + 0x6F: "divide", + 0x70: "f1", + 0x71: "f2", + 0x72: "f3", + 0x73: "f4", + 0x74: "f5", + 0x75: "f6", + 0x76: "f7", + 0x77: "f8", + 0x78: "f9", + 0x79: "f10", + 0x7A: "f11", + 0x7B: "f12", + 0x7C: "f13", + 0x7D: "f14", + 0x7E: "f15", + 0x7F: "f16", + 0x80: "f17", + 0x81: "f18", + 0x82: "f19", + 0x83: "f20", + 0x84: "f21", + 0x85: "f22", + 0x86: "f23", + 0x87: "f24", + 0x88: "navigation_view", + 0x89: "navigation_menu", + 0x8A: "navigation_up", + 0x8B: "navigation_down", + 0x8C: "navigation_left", + 0x8D: "navigation_right", + 0x8E: "navigation_accept", + 0x8F: "navigation_cancel", + 0x90: "numlock", + 0x91: "scroll", + 0x92: "oem_nec_equal", + 0x93: "oem_fj_masshou", + 0x94: "oem_fj_touroku", + 0x95: "oem_fj_loya", + 0x96: "oem_fj_roya", + 0xA0: "lshift", + 0xA1: "rshift", + 0xA2: "lcontrol", + 0xA3: "rcontrol", + 0xA4: "lmenu", + 0xA5: "rmenu", + 0xA6: "browser_back", + 0xA7: "browser_forward", + 0xA8: "browser_refresh", + 0xA9: "browser_stop", + 0xAA: "browser_search", + 0xAB: "browser_favorites", + 0xAC: "browser_home", + 0xAD: "volume_mute", + 0xAE: "volume_down", + 0xAF: "volume_up", + 0xB0: "media_next_track", + 0xB1: "media_prev_track", + 0xB2: "media_stop", + 0xB3: "media_play_pause", + 0xB4: "launch_mail", + 0xB5: "launch_media_select", + 0xB6: "launch_app1", + 0xB7: "launch_app2", + 0xBA: "oem_1", + 0xBB: "oem_plus", + 0xBC: "oem_comma", + 0xBD: "oem_minus", + 0xBE: "oem_period", + 0xBF: "oem_2", + 0xC0: "oem_3", + 0xC3: "gamepad_a", + 0xC4: "gamepad_b", + 0xC5: "gamepad_x", + 0xC6: "gamepad_y", + 0xC7: "gamepad_right_shoulder", + 0xC8: "gamepad_left_shoulder", + 0xC9: "gamepad_left_trigger", + 0xCA: "gamepad_right_trigger", + 0xCB: "gamepad_dpad_up", + 0xCC: "gamepad_dpad_down", + 0xCD: "gamepad_dpad_left", + 0xCE: "gamepad_dpad_right", + 0xCF: "gamepad_menu", + 0xD0: "gamepad_view", + 0xD1: "gamepad_left_thumbstick_button", + 0xD2: "gamepad_right_thumbstick_button", + 0xD3: "gamepad_left_thumbstick_up", + 0xD4: "gamepad_left_thumbstick_down", + 0xD5: "gamepad_left_thumbstick_right", + 0xD6: "gamepad_left_thumbstick_left", + 0xD7: "gamepad_right_thumbstick_up", + 0xD8: "gamepad_right_thumbstick_down", + 0xD9: "gamepad_right_thumbstick_right", + 0xDA: "gamepad_right_thumbstick_left", + 0xDB: "oem_4", + 0xDC: "oem_5", + 0xDD: "oem_6", + 0xDE: "oem_7", + 0xDF: "oem_8", + 0xE1: "oem_ax", + 0xE2: "oem_102", + 0xE3: "ico_help", + 0xE4: "ico_00", + 0xE5: "processkey", + 0xE6: "ico_clear", + 0xE7: "packet", + 0xE9: "oem_reset", + 0xEA: "oem_jump", + 0xEB: "oem_pa1", + 0xEC: "oem_pa2", + 0xED: "oem_pa3", + 0xEE: "oem_wsctrl", + 0xEF: "oem_cusel", + 0xF0: "oem_attn", + 0xF1: "oem_finish", + 0xF2: "oem_copy", + 0xF3: "oem_auto", + 0xF4: "oem_enlw", + 0xF5: "oem_backtab", + 0xF6: "attn", + 0xF7: "crsel", + 0xF8: "exsel", + 0xF9: "ereof", + 0xFA: "play", + 0xFB: "zoom", + 0xFC: "noname", + 0xFD: "pa1", + 0xFE: "oem_clear", +} diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go new file mode 100644 index 000000000..f750efdd0 --- /dev/null +++ b/v3/pkg/application/linux_cgo.go @@ -0,0 +1,1884 @@ +//go:build linux && cgo + +package application + +import ( + "fmt" + "regexp" + "strings" + "sync" + "time" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +/* +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gdk-3.0 + +#include +#include +#include +#include +#include +#include +#ifdef G_APPLICATION_DEFAULT_FLAGS + #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_DEFAULT_FLAGS +#else + #define APPLICATION_DEFAULT_FLAGS G_APPLICATION_FLAGS_NONE +#endif + +typedef struct CallbackID +{ + unsigned int value; +} CallbackID; + +extern void dispatchOnMainThreadCallback(unsigned int); + +static gboolean dispatchCallback(gpointer data) { + struct CallbackID *args = data; + unsigned int cid = args->value; + dispatchOnMainThreadCallback(cid); + free(args); + + return G_SOURCE_REMOVE; +}; + +static void dispatchOnMainThread(unsigned int id) { + CallbackID *args = malloc(sizeof(CallbackID)); + args->value = id; + g_idle_add((GSourceFunc)dispatchCallback, (gpointer)args); +} + +typedef struct WindowEvent { + uint id; + uint event; +} WindowEvent; + +static void save_window_id(void *object, uint value) +{ + g_object_set_data((GObject *)object, "windowid", GUINT_TO_POINTER((guint)value)); +} + +static guint get_window_id(void *object) +{ + return GPOINTER_TO_UINT(g_object_get_data((GObject *)object, "windowid")); +} + +// exported below +void activateLinux(gpointer data); +extern void emit(WindowEvent* data); +extern gboolean handleConfigureEvent(GtkWidget*, GdkEventConfigure*, uintptr_t); +extern gboolean handleDeleteEvent(GtkWidget*, GdkEvent*, uintptr_t); +extern gboolean handleFocusEvent(GtkWidget*, GdkEvent*, uintptr_t); +extern void handleLoadChanged(WebKitWebView*, WebKitLoadEvent, uintptr_t); +void handleClick(void*); +extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); +extern gboolean onMenuButtonEvent(GtkWidget *widget, GdkEventButton *event, uintptr_t user_data); +extern void onUriList(char **extracted, gpointer data); +extern gboolean onKeyPressEvent (GtkWidget *widget, GdkEventKey *event, uintptr_t user_data); +extern void onProcessRequest(WebKitURISchemeRequest *request, uintptr_t user_data); +extern void sendMessageToBackend(WebKitUserContentManager *contentManager, WebKitJavascriptResult *result, void *data); +// exported below (end) + +static void signal_connect(void *widget, char *event, void *cb, void* data) { + // g_signal_connect is a macro and can't be called directly + g_signal_connect(widget, event, cb, data); +} + +static WebKitWebView* webkit_web_view(GtkWidget *webview) { + return WEBKIT_WEB_VIEW(webview); +} + +static void* new_message_dialog(GtkWindow *parent, const gchar *msg, int dialogType, bool hasButtons) { + // gtk_message_dialog_new is variadic! Can't call from cgo directly + GtkWidget *dialog; + int buttonMask; + + // buttons will be added after creation + buttonMask = GTK_BUTTONS_OK; + if (hasButtons) { + buttonMask = GTK_BUTTONS_NONE; + } + + dialog = gtk_message_dialog_new( + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + dialogType, + buttonMask, + "%s", + msg); + + // g_signal_connect_swapped (dialog, + // "response", + // G_CALLBACK (callback), + // dialog); + return dialog; +}; + +extern void messageDialogCB(gint button); + +static void* gtkFileChooserDialogNew(char* title, GtkWindow* window, GtkFileChooserAction action, char* cancelLabel, char* acceptLabel) { + // gtk_file_chooser_dialog_new is variadic! Can't call from cgo directly + return (GtkFileChooser*)gtk_file_chooser_dialog_new( + title, + window, + action, + cancelLabel, + GTK_RESPONSE_CANCEL, + acceptLabel, + GTK_RESPONSE_ACCEPT, + NULL); +} + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scaleFactor; + double rotation; + bool isPrimary; +} Screen; + +// CREDIT: https://github.com/rainycape/magick +#include +#include +#include +#include + +static void fix_signal(int signum) { + struct sigaction st; + + if (sigaction(signum, NULL, &st) < 0) { + goto fix_signal_error; + } + st.sa_flags |= SA_ONSTACK; + if (sigaction(signum, &st, NULL) < 0) { + goto fix_signal_error; + } + return; +fix_signal_error: + fprintf(stderr, "error fixing handler for signal %d, please " + "report this issue to " + "https://github.com/wailsapp/wails: %s\n", + signum, strerror(errno)); +} + +static void install_signal_handlers() { + #if defined(SIGCHLD) + fix_signal(SIGCHLD); + #endif + #if defined(SIGHUP) + fix_signal(SIGHUP); + #endif + #if defined(SIGINT) + fix_signal(SIGINT); + #endif + #if defined(SIGQUIT) + fix_signal(SIGQUIT); + #endif + #if defined(SIGABRT) + fix_signal(SIGABRT); + #endif + #if defined(SIGFPE) + fix_signal(SIGFPE); + #endif + #if defined(SIGTERM) + fix_signal(SIGTERM); + #endif + #if defined(SIGBUS) + fix_signal(SIGBUS); + #endif + #if defined(SIGSEGV) + fix_signal(SIGSEGV); + #endif + #if defined(SIGXCPU) + fix_signal(SIGXCPU); + #endif + #if defined(SIGXFSZ) + fix_signal(SIGXFSZ); + #endif +} + +static int GetNumScreens(){ + return 0; +} + +static void on_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + GtkSelectionData *selection_data, guint target_type, guint time, + gpointer data) +{ + gint length = gtk_selection_data_get_length(selection_data); + + if (length < 0) + { + g_print("DnD failed!\n"); + gtk_drag_finish(context, FALSE, FALSE, time); + } + + gchar *uri_data = (gchar *)gtk_selection_data_get_data(selection_data); + gchar **uri_list = g_uri_list_extract_uris(uri_data); + + onUriList(uri_list, data); + + g_strfreev(uri_list); + gtk_drag_finish(context, TRUE, TRUE, time); +} + +// drag and drop tutorial: https://wiki.gnome.org/Newcomers/OldDragNDropTutorial +static void enableDND(GtkWidget *widget, gpointer data) +{ + GtkTargetEntry *target = gtk_target_entry_new("text/uri-list", 0, 0); + gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, target, 1, GDK_ACTION_COPY); + + signal_connect(widget, "drag-data-received", on_data_received, data); +} +*/ +import "C" + +// Calloc handles alloc/dealloc of C data +type Calloc struct { + pool []unsafe.Pointer +} + +// NewCalloc creates a new allocator +func NewCalloc() Calloc { + return Calloc{} +} + +// String creates a new C string and retains a reference to it +func (c Calloc) String(in string) *C.char { + result := C.CString(in) + c.pool = append(c.pool, unsafe.Pointer(result)) + return result +} + +// Free frees all allocated C memory +func (c Calloc) Free() { + for _, str := range c.pool { + C.free(str) + } + c.pool = []unsafe.Pointer{} +} + +type windowPointer *C.GtkWindow +type identifier C.uint +type pointer unsafe.Pointer +type GSList C.GSList +type GSListPointer *GSList + +var ( + nilPointer pointer = nil + nilRadioGroup GSListPointer = nil +) + +var ( + gtkSignalToMenuItem map[uint]*MenuItem + mainThreadId *C.GThread +) + +var registerURIScheme sync.Once + +func init() { + gtkSignalToMenuItem = map[uint]*MenuItem{} + + mainThreadId = C.g_thread_self() +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + executeOnMainThread(uint(callbackID)) +} + +//export activateLinux +func activateLinux(data pointer) { + processApplicationEvent(C.uint(events.Linux.ApplicationStartup), data) +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint, data pointer) { + event := newApplicationEvent(events.ApplicationEventType(eventID)) + + //if data != nil { + // dataCStrJSON := C.serializationNSDictionary(data) + // if dataCStrJSON != nil { + // defer C.free(unsafe.Pointer(dataCStrJSON)) + // + // dataJSON := C.GoString(dataCStrJSON) + // var result map[string]any + // err := json.Unmarshal([]byte(dataJSON), &result) + // + // if err != nil { + // panic(err) + // } + // + // event.Context().setData(result) + // } + //} + + switch event.Id { + case uint(events.Linux.SystemThemeChanged): + isDark := globalApplication.Env.IsDarkMode() + event.Context().setIsDarkMode(isDark) + } + applicationEvents <- event +} + +func isOnMainThread() bool { + threadId := C.g_thread_self() + return threadId == mainThreadId +} + +// implementation below +func appName() string { + name := C.g_get_application_name() + defer C.free(unsafe.Pointer(name)) + return C.GoString(name) +} + +func appNew(name string) pointer { + C.install_signal_handlers() + + // prevent leading number + if matched, _ := regexp.MatchString(`^\d+`, name); matched { + name = fmt.Sprintf("_%s", name) + } + name = strings.Replace(name, "(", "_", -1) + name = strings.Replace(name, ")", "_", -1) + appId := fmt.Sprintf("org.wails.%s", name) + nameC := C.CString(appId) + defer C.free(unsafe.Pointer(nameC)) + return pointer(C.gtk_application_new(nameC, C.APPLICATION_DEFAULT_FLAGS)) +} + +func setProgramName(prgName string) { + cPrgName := C.CString(prgName) + defer C.free(unsafe.Pointer(cPrgName)) + C.g_set_prgname(cPrgName) +} + +func appRun(app pointer) error { + application := (*C.GApplication)(app) + //TODO: Only set this if we configure it to do so + C.g_application_hold(application) // allows it to run without a window + + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + C.signal_connect(unsafe.Pointer(application), signal, C.activateLinux, nil) + status := C.g_application_run(application, 0, nil) + C.g_application_release(application) + C.g_object_unref(C.gpointer(app)) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + C.g_application_quit((*C.GApplication)(application)) +} + +func (w *linuxWebviewWindow) contextMenuSignals(menu pointer) { + c := NewCalloc() + defer c.Free() + winID := unsafe.Pointer(uintptr(C.uint(w.parent.ID()))) + C.signal_connect(unsafe.Pointer(menu), c.String("button-release-event"), C.onMenuButtonEvent, winID) +} + +func (w *linuxWebviewWindow) contextMenuShow(menu pointer, data *ContextMenuData) { + geometry := C.GdkRectangle{ + x: C.int(data.X), + y: C.int(data.Y), + } + event := C.GdkEvent{} + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + C.gtk_menu_popup_at_rect( + (*C.GtkMenu)(menu), + gdkWindow, + (*C.GdkRectangle)(&geometry), + C.GDK_GRAVITY_NORTH_WEST, + C.GDK_GRAVITY_NORTH_WEST, + (*C.GdkEvent)(&event), + ) + w.ctxMenuOpened = true +} + +func (a *linuxApp) getCurrentWindowID() uint { + // TODO: Add extra metadata to window and use it! + window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(a.application))) + if window == nil { + return uint(1) + } + identifier, ok := a.windowMap[window] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +func (a *linuxApp) getWindows() []pointer { + result := []pointer{} + windows := C.gtk_application_get_windows((*C.GtkApplication)(a.application)) + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func (a *linuxApp) hideAllWindows() { + for _, window := range a.getWindows() { + C.gtk_widget_hide((*C.GtkWidget)(window)) + } +} + +func (a *linuxApp) showAllWindows() { + for _, window := range a.getWindows() { + C.gtk_window_present((*C.GtkWindow)(window)) + } +} + +func (a *linuxApp) setIcon(icon []byte) { + gbytes := C.g_bytes_new_static(C.gconstpointer(unsafe.Pointer(&icon[0])), C.ulong(len(icon))) + stream := C.g_memory_input_stream_new_from_bytes(gbytes) + var gerror *C.GError + pixbuf := C.gdk_pixbuf_new_from_stream(stream, nil, &gerror) + if gerror != nil { + a.parent.error("failed to load application icon: %s", C.GoString(gerror.message)) + C.g_error_free(gerror) + return + } + + a.icon = pointer(pixbuf) +} + +// Clipboard +func clipboardGet() string { + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + text := C.gtk_clipboard_wait_for_text(clip) + return C.GoString(text) +} + +func clipboardSet(text string) { + cText := C.CString(text) + clip := C.gtk_clipboard_get(C.GDK_SELECTION_CLIPBOARD) + C.gtk_clipboard_set_text(clip, cText, -1) + + clip = C.gtk_clipboard_get(C.GDK_SELECTION_PRIMARY) + C.gtk_clipboard_set_text(clip, cText, -1) + C.free(unsafe.Pointer(cText)) +} + +// Menu +func menuAddSeparator(menu *Menu) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((menu.impl).(*linuxMenu).native), + C.gtk_separator_menu_item_new()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + C.gtk_menu_shell_append( + (*C.GtkMenuShell)((parent.impl).(*linuxMenu).native), + (*C.GtkWidget)((menu.impl).(*linuxMenuItem).native), + ) + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(C.gtk_menu_bar_new()) +} + +func menuNew() pointer { + return pointer(C.gtk_menu_new()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + C.gtk_menu_item_set_submenu( + (*C.GtkMenuItem)((item.impl).(*linuxMenuItem).native), + (*C.GtkWidget)((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(item.native))) +} + +//export handleClick +func handleClick(idPtr unsafe.Pointer) { + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + value := C.g_object_get_data((*C.GObject)(idPtr), ident) + id := uint(*(*C.uint)(value)) + item, ok := gtkSignalToMenuItem[id] + if !ok { + return + } + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } +} + +func attachMenuHandler(item *MenuItem) uint { + signal := C.CString("activate") + defer C.free(unsafe.Pointer(signal)) + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := C.GConnectFlags(0) + handlerId := C.g_signal_connect_object( + C.gpointer(widget), + signal, + C.GCallback(C.handleClick), + C.gpointer(widget), + flags) + + id := C.uint(item.id) + ident := C.CString("id") + defer C.free(unsafe.Pointer(ident)) + C.g_object_set_data( + (*C.GObject)(widget), + ident, + C.gpointer(&id), + ) + + gtkSignalToMenuItem[item.id] = item + return uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(widget)) == C.int(1) { + return true + } + return false +} + +func menuItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_menu_item_new(), label, bitmap) +} + +func menuItemDestroy(widget pointer) { + C.gtk_widget_destroy((*C.GtkWidget)(widget)) +} + +func menuItemAddProperties(menuItem *C.GtkWidget, label string, bitmap []byte) pointer { + /* + // FIXME: Support accelerator configuration + activate := C.CString("activate") + defer C.free(unsafe.Pointer(activate)) + accelGroup := C.gtk_accel_group_new() + C.gtk_widget_add_accelerator(menuItem, activate, accelGroup, + C.GDK_KEY_m, C.GDK_CONTROL_MASK, C.GTK_ACCEL_VISIBLE) + */ + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + lbl := unsafe.Pointer(C.gtk_accel_label_new(cLabel)) + C.gtk_label_set_use_underline((*C.GtkLabel)(lbl), 1) + C.gtk_label_set_xalign((*C.GtkLabel)(lbl), 0.0) + C.gtk_accel_label_set_accel_widget( + (*C.GtkAccelLabel)(lbl), + (*C.GtkWidget)(unsafe.Pointer(menuItem))) + + box := C.gtk_box_new(C.GTK_ORIENTATION_HORIZONTAL, 6) + if img, err := pngToImage(bitmap); err == nil { + gbytes := C.g_bytes_new_static(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + + C.gtk_box_pack_end( + (*C.GtkBox)(unsafe.Pointer(box)), + (*C.GtkWidget)(lbl), 1, 1, 0) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(menuItem)), + (*C.GtkWidget)(unsafe.Pointer(box))) + C.gtk_widget_show_all(menuItem) + return pointer(menuItem) +} + +func menuCheckItemNew(label string, bitmap []byte) pointer { + return menuItemAddProperties(C.gtk_check_menu_item_new(), label, bitmap) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := C.int(0) + if checked { + value = C.int(1) + } + C.gtk_check_menu_item_set_active( + (*C.GtkCheckMenuItem)(widget), + value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := C.int(1) + if disabled { + value = C.int(0) + } + C.gtk_widget_set_sensitive( + (*C.GtkWidget)(widget), + value) +} + +func menuItemSetLabel(widget pointer, label string) { + value := C.CString(label) + C.gtk_menu_item_set_label( + (*C.GtkMenuItem)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemRemoveBitmap(widget pointer) { + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if box == nil { + return + } + + children := C.gtk_container_get_children((*C.GtkContainer)(unsafe.Pointer(box))) + defer C.g_list_free(children) + count := int(C.g_list_length(children)) + if count == 2 { + C.gtk_container_remove((*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(children.data)) + } +} + +func menuItemSetBitmap(widget pointer, bitmap []byte) { + menuItemRemoveBitmap(widget) + box := C.gtk_bin_get_child((*C.GtkBin)(widget)) + if img, err := pngToImage(bitmap); err == nil { + gbytes := C.g_bytes_new_static(C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(box)), + (*C.GtkWidget)(unsafe.Pointer(image))) + } + +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + value := C.CString(tooltip) + C.gtk_widget_set_tooltip_text( + (*C.GtkWidget)(widget), + value) + C.free(unsafe.Pointer(value)) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + C.g_signal_handler_block(C.gpointer(widget), C.ulong(handlerId)) + } else { + C.g_signal_handler_unblock(C.gpointer(widget), C.ulong(handlerId)) + } +} + +func menuRadioItemNew(group *GSList, label string) pointer { + cLabel := C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + return pointer(C.gtk_radio_menu_item_new_with_label((*C.GSList)(group), cLabel)) +} + +// screen related + +func getScreenByIndex(display *C.struct__GdkDisplay, index int) *Screen { + monitor := C.gdk_display_get_monitor(display, C.int(index)) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + var geometry C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &geometry) + primary := false + if C.gdk_monitor_is_primary(monitor) == 1 { + primary = true + } + name := C.gdk_monitor_get_model(monitor) + return &Screen{ + ID: fmt.Sprintf("%d", index), + Name: C.GoString(name), + IsPrimary: primary, + ScaleFactor: float32(C.gdk_monitor_get_scale_factor(monitor)), + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + PhysicalBounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + WorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + PhysicalWorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + Rotation: 0.0, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := C.gtk_application_get_active_window((*C.GtkApplication)(app)) + gdkWindow := C.gtk_widget_get_window((*C.GtkWidget)(unsafe.Pointer(window))) + display := C.gdk_window_get_display(gdkWindow) + count := C.gdk_display_get_n_monitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets + +func (w *linuxWebviewWindow) setEnabled(enabled bool) { + var value C.int + if enabled { + value = C.int(1) + } + + C.gtk_widget_set_sensitive(w.gtkWidget(), value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + C.gtk_widget_hide((*C.GtkWidget)(widget)) + } else { + C.gtk_widget_show((*C.GtkWidget)(widget)) + } +} + +func (w *linuxWebviewWindow) close() { + C.gtk_widget_destroy(w.gtkWidget()) + getNativeApplication().unregisterWindow(windowPointer(w.window)) +} + +func (w *linuxWebviewWindow) enableDND() { + C.gtk_drag_dest_unset((*C.GtkWidget)(w.webview)) + + windowId := C.uint(w.parent.id) + C.enableDND((*C.GtkWidget)(w.vbox), C.gpointer(&windowId)) +} + +func (w *linuxWebviewWindow) execJS(js string) { + InvokeAsync(func() { + value := C.CString(js) + C.webkit_web_view_evaluate_javascript(w.webKitWebView(), + value, + C.long(len(js)), + nil, + C.CString(""), + nil, + nil, + nil) + C.free(unsafe.Pointer(value)) + }) +} + +func getMousePosition() (int, int, *Screen) { + var x, y C.gint + var screen *C.GdkScreen + defaultDisplay := C.gdk_display_get_default() + device := C.gdk_seat_get_pointer(C.gdk_display_get_default_seat(defaultDisplay)) + C.gdk_device_get_position(device, &screen, &x, &y) + // Get Monitor for screen + monitor := C.gdk_display_get_monitor_at_point(defaultDisplay, x, y) + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + scaleFactor := int(C.gdk_monitor_get_scale_factor(monitor)) + return int(x), int(y), &Screen{ + ID: fmt.Sprintf("%d", 0), // A unique identifier for the display + Name: C.GoString(C.gdk_monitor_get_model(monitor)), // The name of the display + ScaleFactor: float32(scaleFactor), // The scale factor of the display + X: int(geometry.x), // The x-coordinate of the top-left corner of the rectangle + Y: int(geometry.y), // The y-coordinate of the top-left corner of the rectangle + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + WorkArea: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + IsPrimary: false, + Rotation: 0.0, + } +} + +func (w *linuxWebviewWindow) destroy() { + w.parent.markAsDestroyed() + // Free menu + if w.gtkmenu != nil { + C.gtk_widget_destroy((*C.GtkWidget)(w.gtkmenu)) + w.gtkmenu = nil + } + // Free window + C.gtk_widget_destroy(w.gtkWidget()) +} + +func (w *linuxWebviewWindow) fullscreen() { + w.maximise() + //w.lastWidth, w.lastHeight = w.size() + x, y, width, height, scaleFactor := w.getCurrentMonitorGeometry() + if x == -1 && y == -1 && width == -1 && height == -1 { + return + } + w.setMinMaxSize(0, 0, width*scaleFactor, height*scaleFactor) + w.setSize(width*scaleFactor, height*scaleFactor) + C.gtk_window_fullscreen(w.gtkWindow()) + w.setRelativePosition(0, 0) +} + +func (w *linuxWebviewWindow) getCurrentMonitor() *C.GdkMonitor { + // Get the monitor that the window is currently on + display := C.gtk_widget_get_display(w.gtkWidget()) + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + if gdkWindow == nil { + return nil + } + return C.gdk_display_get_monitor_at_window(display, gdkWindow) +} + +func (w *linuxWebviewWindow) getScreen() (*Screen, error) { + // Get the current screen for the window + monitor := w.getCurrentMonitor() + name := C.gdk_monitor_get_model(monitor) + mx, my, width, height, scaleFactor := w.getCurrentMonitorGeometry() + return &Screen{ + ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display + Name: C.GoString(name), // The name of the display + ScaleFactor: float32(scaleFactor), // The scale factor of the display + X: mx, // The x-coordinate of the top-left corner of the rectangle + Y: my, // The y-coordinate of the top-left corner of the rectangle + Size: Size{ + Height: int(height), + Width: int(width), + }, + Bounds: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + WorkArea: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + PhysicalBounds: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + PhysicalWorkArea: Rect{ + X: int(mx), + Y: int(my), + Height: int(height), + Width: int(width), + }, + IsPrimary: false, + Rotation: 0.0, + }, nil +} + +func (w *linuxWebviewWindow) getCurrentMonitorGeometry() (x int, y int, width int, height int, scaleFactor int) { + monitor := w.getCurrentMonitor() + if monitor == nil { + // Best effort to find screen resolution of default monitor + display := C.gdk_display_get_default() + monitor = C.gdk_display_get_primary_monitor(display) + if monitor == nil { + return -1, -1, -1, -1, 1 + } + } + var result C.GdkRectangle + C.gdk_monitor_get_geometry(monitor, &result) + scaleFactor = int(C.gdk_monitor_get_scale_factor(monitor)) + return int(result.x), int(result.y), int(result.width), int(result.height), scaleFactor +} + +func (w *linuxWebviewWindow) size() (int, int) { + var windowWidth C.int + var windowHeight C.int + C.gtk_window_get_size(w.gtkWindow(), &windowWidth, &windowHeight) + return int(windowWidth), int(windowHeight) +} + +func (w *linuxWebviewWindow) relativePosition() (int, int) { + x, y := w.position() + // The position must be relative to the screen it is on + // We need to get the screen it is on + monitor := w.getCurrentMonitor() + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + x = x - int(geometry.x) + y = y - int(geometry.y) + + // TODO: Scale based on DPI + + return x, y +} + +func (w *linuxWebviewWindow) gtkWidget() *C.GtkWidget { + return (*C.GtkWidget)(w.window) +} + +func (w *linuxWebviewWindow) windowHide() { + C.gtk_widget_hide(w.gtkWidget()) +} + +func (w *linuxWebviewWindow) isFullscreen() bool { + gdkWindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkWindow) + return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0 +} + +func (w *linuxWebviewWindow) isFocused() bool { + // returns true if window is focused + return C.gtk_window_has_toplevel_focus(w.gtkWindow()) == 1 +} + +func (w *linuxWebviewWindow) isMaximised() bool { + gdkwindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_MAXIMIZED > 0 && state&C.GDK_WINDOW_STATE_FULLSCREEN == 0 +} + +func (w *linuxWebviewWindow) isMinimised() bool { + gdkwindow := C.gtk_widget_get_window(w.gtkWidget()) + state := C.gdk_window_get_state(gdkwindow) + return state&C.GDK_WINDOW_STATE_ICONIFIED > 0 +} + +func (w *linuxWebviewWindow) isVisible() bool { + if C.gtk_widget_is_visible(w.gtkWidget()) == 1 { + return true + } + return false +} + +func (w *linuxWebviewWindow) maximise() { + C.gtk_window_maximize(w.gtkWindow()) +} + +func (w *linuxWebviewWindow) minimise() { + C.gtk_window_iconify(w.gtkWindow()) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy WebviewGpuPolicy) (window, webview, vbox pointer) { + window = pointer(C.gtk_application_window_new((*C.GtkApplication)(application))) + C.g_object_ref_sink(C.gpointer(window)) + webview = windowNewWebview(windowId, gpuPolicy) + vbox = pointer(C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)) + name := C.CString("webview-box") + defer C.free(unsafe.Pointer(name)) + C.gtk_widget_set_name((*C.GtkWidget)(vbox), name) + + C.gtk_container_add((*C.GtkContainer)(window), (*C.GtkWidget)(vbox)) + if menu != nil { + C.gtk_box_pack_start((*C.GtkBox)(vbox), (*C.GtkWidget)(menu), 0, 0, 0) + } + C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(vbox)), (*C.GtkWidget)(webview), 1, 1, 0) + return +} + +func windowNewWebview(parentId uint, gpuPolicy WebviewGpuPolicy) pointer { + c := NewCalloc() + defer c.Free() + manager := C.webkit_user_content_manager_new() + C.webkit_user_content_manager_register_script_message_handler(manager, c.String("external")) + webView := C.webkit_web_view_new_with_user_content_manager(manager) + + // attach window id to both the webview and contentmanager + C.save_window_id(unsafe.Pointer(webView), C.uint(parentId)) + C.save_window_id(unsafe.Pointer(manager), C.uint(parentId)) + + registerURIScheme.Do(func() { + context := C.webkit_web_view_get_context(C.webkit_web_view(webView)) + C.webkit_web_context_register_uri_scheme( + context, + c.String("wails"), + C.WebKitURISchemeRequestCallback(C.onProcessRequest), + nil, + nil) + }) + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(unsafe.Pointer(webView))) + C.webkit_settings_set_user_agent_with_application_details(settings, c.String("wails.io"), c.String("")) + + switch gpuPolicy { + case WebviewGpuPolicyAlways: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS) + break + case WebviewGpuPolicyOnDemand: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + break + case WebviewGpuPolicyNever: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER) + break + default: + C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) + } + return pointer(webView) +} + +func (w *linuxWebviewWindow) present() { + C.gtk_window_present(w.gtkWindow()) + // gtk_window_unminimize (w.gtkWindow()) /// gtk4 +} + +func (w *linuxWebviewWindow) setSize(width, height int) { + C.gtk_window_resize( + w.gtkWindow(), + C.gint(width), + C.gint(height)) +} + +func (w *linuxWebviewWindow) windowShow() { + if w.gtkWidget() == nil { + return + } + C.gtk_widget_show_all(w.gtkWidget()) +} + +func windowIgnoreMouseEvents(window pointer, webview pointer, ignore bool) { + var enable C.int + if ignore { + enable = 1 + } + gdkWindow := (*C.GdkWindow)(window) + C.gdk_window_set_pass_through(gdkWindow, enable) + C.webkit_web_view_set_editable((*C.WebKitWebView)(webview), C.gboolean(enable)) +} + +func (w *linuxWebviewWindow) webKitWebView() *C.WebKitWebView { + return (*C.WebKitWebView)(w.webview) +} + +func (w *linuxWebviewWindow) setBorderless(borderless bool) { + C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!borderless)) +} + +func (w *linuxWebviewWindow) setResizable(resizable bool) { + C.gtk_window_set_resizable(w.gtkWindow(), gtkBool(resizable)) +} + +func (w *linuxWebviewWindow) setDefaultSize(width int, height int) { + C.gtk_window_set_default_size(w.gtkWindow(), C.gint(width), C.gint(height)) +} + +func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) { + rgba := C.GdkRGBA{C.double(colour.Red) / 255.0, C.double(colour.Green) / 255.0, C.double(colour.Blue) / 255.0, C.double(colour.Alpha) / 255.0} + C.webkit_web_view_set_background_color((*C.WebKitWebView)(w.webview), &rgba) + + colour.Alpha = 255 + cssStr := C.CString(fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0)) + provider := C.gtk_css_provider_new() + C.gtk_style_context_add_provider( + C.gtk_widget_get_style_context((*C.GtkWidget)(w.vbox)), + (*C.GtkStyleProvider)(unsafe.Pointer(provider)), + C.GTK_STYLE_PROVIDER_PRIORITY_USER) + C.g_object_unref(C.gpointer(provider)) + C.gtk_css_provider_load_from_data(provider, cssStr, -1, nil) + C.free(unsafe.Pointer(cssStr)) +} + +func getPrimaryScreen() (*Screen, error) { + display := C.gdk_display_get_default() + monitor := C.gdk_display_get_primary_monitor(display) + geometry := C.GdkRectangle{} + C.gdk_monitor_get_geometry(monitor, &geometry) + scaleFactor := int(C.gdk_monitor_get_scale_factor(monitor)) + // get the name for the screen + name := C.gdk_monitor_get_model(monitor) + return &Screen{ + ID: "0", + Name: C.GoString(name), + IsPrimary: true, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + Bounds: Rect{ + X: int(geometry.x), + Y: int(geometry.y), + Height: int(geometry.height), + Width: int(geometry.width), + }, + ScaleFactor: float32(scaleFactor), + }, nil +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := C.GdkGeometry{ + min_width: C.int(minWidth), + min_height: C.int(minHeight), + max_width: C.int(maxWidth), + max_height: C.int(maxHeight), + } + C.gtk_window_set_geometry_hints((*C.GtkWindow)(window), nil, &size, C.GDK_HINT_MAX_SIZE|C.GDK_HINT_MIN_SIZE) +} + +func (w *linuxWebviewWindow) setFrameless(frameless bool) { + C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!frameless)) + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func (w *linuxWebviewWindow) setHTML(html string) { + cHTML := C.CString(html) + uri := C.CString("wails://") + empty := C.CString("") + defer C.free(unsafe.Pointer(cHTML)) + defer C.free(unsafe.Pointer(uri)) + defer C.free(unsafe.Pointer(empty)) + C.webkit_web_view_load_alternate_html( + w.webKitWebView(), + cHTML, + uri, + empty) +} + +func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + C.gtk_window_set_keep_above(w.gtkWindow(), gtkBool(alwaysOnTop)) +} + +func (w *linuxWebviewWindow) flash(_ bool) { + // Not supported on Linux +} + +func (w *linuxWebviewWindow) setTitle(title string) { + if !w.parent.options.Frameless { + cTitle := C.CString(title) + C.gtk_window_set_title(w.gtkWindow(), cTitle) + C.free(unsafe.Pointer(cTitle)) + } +} + +func (w *linuxWebviewWindow) setIcon(icon pointer) { + if icon != nil { + C.gtk_window_set_icon(w.gtkWindow(), (*C.GdkPixbuf)(icon)) + } +} + +func (w *linuxWebviewWindow) gtkWindow() *C.GtkWindow { + return (*C.GtkWindow)(w.window) +} + +func (w *linuxWebviewWindow) setTransparent() { + screen := C.gtk_widget_get_screen(w.gtkWidget()) + visual := C.gdk_screen_get_rgba_visual(screen) + + if visual != nil && C.gdk_screen_is_composited(screen) == C.int(1) { + C.gtk_widget_set_app_paintable(w.gtkWidget(), C.gboolean(1)) + C.gtk_widget_set_visual(w.gtkWidget(), visual) + } +} + +func (w *linuxWebviewWindow) setURL(uri string) { + target := C.CString(uri) + C.webkit_web_view_load_uri(w.webKitWebView(), target) + C.free(unsafe.Pointer(target)) +} + +//export emit +func emit(we *C.WindowEvent) { + window, _ := globalApplication.Window.GetByID(uint(we.id)) + if window != nil { + windowEvents <- &windowEvent{ + WindowID: window.ID(), + EventID: uint(events.WindowEventType(we.event)), + } + } +} + +//export handleConfigureEvent +func handleConfigureEvent(widget *C.GtkWidget, event *C.GdkEventConfigure, data C.uintptr_t) C.gboolean { + window, _ := globalApplication.Window.GetByID(uint(data)) + if window != nil { + lw, ok := window.(*WebviewWindow).impl.(*linuxWebviewWindow) + if !ok { + return C.gboolean(1) + } + if lw.lastX != int(event.x) || lw.lastY != int(event.y) { + lw.moveDebouncer(func() { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDidMove)) + }) + } + + if lw.lastWidth != int(event.width) || lw.lastHeight != int(event.height) { + lw.resizeDebouncer(func() { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDidResize)) + }) + } + + lw.lastX = int(event.x) + lw.lastY = int(event.y) + lw.lastWidth = int(event.width) + lw.lastHeight = int(event.height) + } + + return C.gboolean(0) +} + +//export handleDeleteEvent +func handleDeleteEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.uintptr_t) C.gboolean { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowDeleteEvent)) + return C.gboolean(1) +} + +//export handleFocusEvent +func handleFocusEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.uintptr_t) C.gboolean { + focusEvent := (*C.GdkEventFocus)(unsafe.Pointer(event)) + if focusEvent._type == C.GDK_FOCUS_CHANGE { + if focusEvent.in == C.TRUE { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowFocusIn)) + } else { + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowFocusOut)) + } + } + return C.gboolean(0) +} + +//export handleLoadChanged +func handleLoadChanged(webview *C.WebKitWebView, event C.WebKitLoadEvent, data C.uintptr_t) { + switch event { + case C.WEBKIT_LOAD_FINISHED: + processWindowEvent(C.uint(data), C.uint(events.Linux.WindowLoadChanged)) + } +} + +func (w *linuxWebviewWindow) setupSignalHandlers(emit func(e events.WindowEventType)) { + + c := NewCalloc() + defer c.Free() + + winID := unsafe.Pointer(uintptr(C.uint(w.parent.ID()))) + + // Set up the window close event + wv := unsafe.Pointer(w.webview) + C.signal_connect(unsafe.Pointer(w.window), c.String("delete-event"), C.handleDeleteEvent, winID) + C.signal_connect(unsafe.Pointer(w.window), c.String("focus-out-event"), C.handleFocusEvent, winID) + C.signal_connect(wv, c.String("load-changed"), C.handleLoadChanged, winID) + C.signal_connect(unsafe.Pointer(w.window), c.String("configure-event"), C.handleConfigureEvent, winID) + + contentManager := C.webkit_web_view_get_user_content_manager(w.webKitWebView()) + C.signal_connect(unsafe.Pointer(contentManager), c.String("script-message-received::external"), C.sendMessageToBackend, nil) + C.signal_connect(wv, c.String("button-press-event"), C.onButtonEvent, winID) + C.signal_connect(wv, c.String("button-release-event"), C.onButtonEvent, winID) + C.signal_connect(wv, c.String("key-press-event"), C.onKeyPressEvent, winID) +} + +func getMouseButtons() (bool, bool, bool) { + var pointer *C.GdkDevice + var state C.GdkModifierType + pointer = C.gdk_seat_get_pointer(C.gdk_display_get_default_seat(C.gdk_display_get_default())) + C.gdk_device_get_state(pointer, nil, nil, &state) + return state&C.GDK_BUTTON1_MASK > 0, state&C.GDK_BUTTON2_MASK > 0, state&C.GDK_BUTTON3_MASK > 0 +} + +func openDevTools(webview pointer) { + inspector := C.webkit_web_view_get_inspector((*C.WebKitWebView)(webview)) + C.webkit_web_inspector_show(inspector) +} + +func (w *linuxWebviewWindow) startDrag() error { + C.gtk_window_begin_move_drag( + (*C.GtkWindow)(w.window), + C.int(w.drag.MouseButton), + C.int(w.drag.XRoot), + C.int(w.drag.YRoot), + C.uint32_t(w.drag.DragTime)) + return nil +} + +func enableDevTools(webview pointer) { + settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(webview)) + enabled := C.webkit_settings_get_enable_developer_extras(settings) + switch enabled { + case C.int(0): + enabled = C.int(1) + case C.int(1): + enabled = C.int(0) + } + C.webkit_settings_set_enable_developer_extras(settings, enabled) +} + +func (w *linuxWebviewWindow) unfullscreen() { + C.gtk_window_unfullscreen((*C.GtkWindow)(w.window)) + w.unmaximise() +} + +func (w *linuxWebviewWindow) unmaximise() { + C.gtk_window_unmaximize((*C.GtkWindow)(w.window)) +} + +func (w *linuxWebviewWindow) getZoom() float64 { + return float64(C.webkit_web_view_get_zoom_level(w.webKitWebView())) +} + +func (w *linuxWebviewWindow) zoomIn() { + // FIXME: ZoomIn/Out is assumed to be incorrect! + ZoomInFactor := 1.10 + w.setZoom(w.getZoom() * ZoomInFactor) +} + +func (w *linuxWebviewWindow) zoomOut() { + ZoomInFactor := -1.10 + w.setZoom(w.getZoom() * ZoomInFactor) +} + +func (w *linuxWebviewWindow) zoomReset() { + w.setZoom(1.0) +} + +func (w *linuxWebviewWindow) reload() { + uri := C.CString("wails://") + C.webkit_web_view_load_uri(w.webKitWebView(), uri) + C.free(unsafe.Pointer(uri)) +} + +func (w *linuxWebviewWindow) setZoom(zoom float64) { + if zoom < 1 { // 1.0 is the smallest allowable + zoom = 1 + } + C.webkit_web_view_set_zoom_level(w.webKitWebView(), C.double(zoom)) +} + +func (w *linuxWebviewWindow) move(x, y int) { + // Move the window to these coordinates + C.gtk_window_move(w.gtkWindow(), C.int(x), C.int(y)) +} + +func (w *linuxWebviewWindow) position() (int, int) { + var x C.int + var y C.int + C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) + return int(x), int(y) +} + +func (w *linuxWebviewWindow) ignoreMouse(ignore bool) { + if ignore { + C.gtk_widget_set_events((*C.GtkWidget)(unsafe.Pointer(w.window)), C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK) + } else { + C.gtk_widget_set_events((*C.GtkWidget)(unsafe.Pointer(w.window)), C.GDK_ALL_EVENTS_MASK) + } +} + +// FIXME Change this to reflect mouse button! +// +//export onButtonEvent +func onButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data C.uintptr_t) C.gboolean { + // Constants (defined here to be easier to use with purego) + GdkButtonPress := C.GDK_BUTTON_PRESS // 4 + Gdk2ButtonPress := C.GDK_2BUTTON_PRESS // 5 for double-click + GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 + + windowId := uint(C.uint(data)) + window, _ := globalApplication.Window.GetByID(windowId) + if window == nil { + return C.gboolean(0) + } + lw, ok := (window.(*WebviewWindow).impl).(*linuxWebviewWindow) + if !ok { + return C.gboolean(0) + } + + if event == nil { + return C.gboolean(0) + } + if event.button == 3 { + return C.gboolean(0) + } + + switch int(event._type) { + case GdkButtonPress: + lw.drag.MouseButton = uint(event.button) + lw.drag.XRoot = int(event.x_root) + lw.drag.YRoot = int(event.y_root) + lw.drag.DragTime = uint32(event.time) + case Gdk2ButtonPress: + // do we need something here? + case GdkButtonRelease: + lw.endDrag(uint(event.button), int(event.x_root), int(event.y_root)) + } + + return C.gboolean(0) +} + +//export onMenuButtonEvent +func onMenuButtonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data C.uintptr_t) C.gboolean { + // Constants (defined here to be easier to use with purego) + GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 + + windowId := uint(C.uint(data)) + window, _ := globalApplication.Window.GetByID(windowId) + if window == nil { + return C.gboolean(0) + } + lw, ok := (window.(*WebviewWindow).impl).(*linuxWebviewWindow) + if !ok { + return C.gboolean(0) + } + + // prevent custom context menu from closing immediately + if event.button == 3 && int(event._type) == GdkButtonRelease && lw.ctxMenuOpened { + lw.ctxMenuOpened = false + return C.gboolean(1) + } + + return C.gboolean(0) +} + +//export onUriList +func onUriList(extracted **C.char, data unsafe.Pointer) { + // Credit: https://groups.google.com/g/golang-nuts/c/bI17Bpck8K4/m/DVDa7EMtDAAJ + offset := unsafe.Sizeof(uintptr(0)) + filenames := []string{} + for *extracted != nil { + filenames = append(filenames, strings.TrimPrefix(C.GoString(*extracted), "file://")) + extracted = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(extracted)) + offset)) + } + + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: uint(*((*C.uint)(data))), + filenames: filenames, + } +} + +var debounceTimer *time.Timer +var isDebouncing bool = false + +//export onKeyPressEvent +func onKeyPressEvent(_ *C.GtkWidget, event *C.GdkEventKey, userData C.uintptr_t) C.gboolean { + // Keypress re-emits if the key is pressed over a certain threshold so we need a debounce + if isDebouncing { + debounceTimer.Reset(50 * time.Millisecond) + return C.gboolean(0) + } + + // Start the debounce + isDebouncing = true + debounceTimer = time.AfterFunc(50*time.Millisecond, func() { + isDebouncing = false + }) + + windowID := uint(C.uint(userData)) + if accelerator, ok := getKeyboardState(event); ok { + windowKeyEvents <- &windowKeyEvent{ + windowId: windowID, + acceleratorString: accelerator, + } + } + return C.gboolean(0) +} + +func getKeyboardState(event *C.GdkEventKey) (string, bool) { + modifiers := uint(event.state) & C.GDK_MODIFIER_MASK + keyCode := uint(event.keyval) + + var acc accelerator + // Check Accelerators + if modifiers&(C.GDK_SHIFT_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, ShiftKey) + } + if modifiers&(C.GDK_CONTROL_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, ControlKey) + } + if modifiers&(C.GDK_MOD1_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, OptionOrAltKey) + } + if modifiers&(C.GDK_SUPER_MASK) != 0 { + acc.Modifiers = append(acc.Modifiers, SuperKey) + } + keyString, ok := VirtualKeyCodes[keyCode] + if !ok { + return "", false + } + acc.Key = keyString + return acc.String(), true +} + +//export onProcessRequest +func onProcessRequest(request *C.WebKitURISchemeRequest, data C.uintptr_t) { + webView := C.webkit_uri_scheme_request_get_web_view(request) + windowId := uint(C.get_window_id(unsafe.Pointer(webView))) + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(unsafe.Pointer(request)), + windowId: windowId, + windowName: func() string { + if window, ok := globalApplication.Window.GetByID(windowId); ok { + return window.Name() + } + return "" + }(), + } +} + +//export sendMessageToBackend +func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.WebKitJavascriptResult, + data unsafe.Pointer) { + + // Get the windowID from the contentManager + thisWindowID := uint(C.get_window_id(unsafe.Pointer(contentManager))) + + var msg string + value := C.webkit_javascript_result_get_js_value(result) + message := C.jsc_value_to_string(value) + msg = C.GoString(message) + defer C.g_free(C.gpointer(message)) + windowMessageBuffer <- &windowMessage{ + windowId: thisWindowID, + message: msg, + } +} + +func gtkBool(input bool) C.gboolean { + if input { + return C.gboolean(1) + } + return C.gboolean(0) +} + +// dialog related + +func setWindowIcon(window pointer, icon []byte) { + loader := C.gdk_pixbuf_loader_new() + if loader == nil { + return + } + written := C.gdk_pixbuf_loader_write( + loader, + (*C.uchar)(&icon[0]), + C.ulong(len(icon)), + nil) + if written == 0 { + return + } + C.gdk_pixbuf_loader_close(loader, nil) + pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader) + if pixbuf != nil { + C.gtk_window_set_icon((*C.GtkWindow)(window), pixbuf) + } + C.g_object_unref(C.gpointer(loader)) +} + +//export messageDialogCB +func messageDialogCB(button C.int) { + fmt.Println("messageDialogCB", button) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) (chan string, error) { + titleStr := C.CString(title) + defer C.free(unsafe.Pointer(titleStr)) + cancelStr := C.CString("_Cancel") + defer C.free(unsafe.Pointer(cancelStr)) + acceptLabelStr := C.CString(acceptLabel) + defer C.free(unsafe.Pointer(acceptLabelStr)) + + fc := C.gtkFileChooserDialogNew( + titleStr, + (*C.GtkWindow)(window), + C.GtkFileChooserAction(action), + cancelStr, + acceptLabelStr) + + C.gtk_file_chooser_set_action((*C.GtkFileChooser)(fc), C.GtkFileChooserAction(action)) + + gtkFilters := []*C.GtkFileFilter{} + for _, filter := range filters { + f := C.gtk_file_filter_new() + displayStr := C.CString(filter.DisplayName) + C.gtk_file_filter_set_name(f, displayStr) + C.free(unsafe.Pointer(displayStr)) + patterns := strings.Split(filter.Pattern, ";") + for _, pattern := range patterns { + patternStr := C.CString(strings.TrimSpace(pattern)) + C.gtk_file_filter_add_pattern(f, patternStr) + C.free(unsafe.Pointer(patternStr)) + } + C.gtk_file_chooser_add_filter((*C.GtkFileChooser)(fc), f) + gtkFilters = append(gtkFilters, f) + } + C.gtk_file_chooser_set_select_multiple( + (*C.GtkFileChooser)(fc), + gtkBool(allowMultiple)) + C.gtk_file_chooser_set_create_folders( + (*C.GtkFileChooser)(fc), + gtkBool(createFolders)) + C.gtk_file_chooser_set_show_hidden( + (*C.GtkFileChooser)(fc), + gtkBool(showHidden)) + + if currentFolder != "" { + path := C.CString(currentFolder) + C.gtk_file_chooser_set_current_folder( + (*C.GtkFileChooser)(fc), + path) + C.free(unsafe.Pointer(path)) + } + + // FIXME: This should be consolidated - duplicate exists in linux_purego.go + buildStringAndFree := func(s C.gpointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + C.g_free(s) // so we don't have to iterate a second time + return string(bytes) + } + + selections := make(chan string) + // run this on the gtk thread + InvokeAsync(func() { + response := C.gtk_dialog_run((*C.GtkDialog)(fc)) + go func() { + defer handlePanic() + if response == C.GTK_RESPONSE_ACCEPT { + filenames := C.gtk_file_chooser_get_filenames((*C.GtkFileChooser)(fc)) + iter := filenames + count := 0 + for { + selections <- buildStringAndFree(C.gpointer(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + } + close(selections) + }() + }) + C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(fc))) + return selections, nil +} + +func runOpenFileDialog(dialog *OpenFileDialogStruct) (chan string, error) { + var action int + + if dialog.canChooseDirectories { + action = C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + } else { + action = C.GTK_FILE_CHOOSER_ACTION_OPEN + } + + window := nilPointer + if dialog.window != nil { + window = (dialog.window.impl).(*linuxWebviewWindow).window + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + action, + buttonText, + dialog.filters) +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + cMsg := C.CString(options.Message) + cTitle := C.CString(options.Title) + defer C.free(unsafe.Pointer(cMsg)) + defer C.free(unsafe.Pointer(cTitle)) + hasButtons := false + if len(options.Buttons) > 0 { + hasButtons = true + } + + dType, ok := map[DialogType]C.int{ + InfoDialogType: C.GTK_MESSAGE_INFO, + // ErrorDialogType: + QuestionDialogType: C.GTK_MESSAGE_QUESTION, + WarningDialogType: C.GTK_MESSAGE_WARNING, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = C.GTK_MESSAGE_INFO + } + + dialog := C.new_message_dialog((*C.GtkWindow)(parent), cMsg, dType, C.bool(hasButtons)) + if options.Title != "" { + C.gtk_window_set_title( + (*C.GtkWindow)(unsafe.Pointer(dialog)), + cTitle) + } + + if img, err := pngToImage(options.Icon); err == nil { + gbytes := C.g_bytes_new_static( + C.gconstpointer(unsafe.Pointer(&img.Pix[0])), + C.ulong(len(img.Pix))) + defer C.g_bytes_unref(gbytes) + pixBuf := C.gdk_pixbuf_new_from_bytes( + gbytes, + C.GDK_COLORSPACE_RGB, + 1, // has_alpha + 8, + C.int(img.Bounds().Dx()), + C.int(img.Bounds().Dy()), + C.int(img.Stride), + ) + image := C.gtk_image_new_from_pixbuf(pixBuf) + C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1)) + contentArea := C.gtk_dialog_get_content_area((*C.GtkDialog)(dialog)) + C.gtk_container_add( + (*C.GtkContainer)(unsafe.Pointer(contentArea)), + (*C.GtkWidget)(image)) + } + for i, button := range options.Buttons { + cLabel := C.CString(button.Label) + defer C.free(unsafe.Pointer(cLabel)) + index := C.int(i) + C.gtk_dialog_add_button( + (*C.GtkDialog)(dialog), cLabel, index) + if button.IsDefault { + C.gtk_dialog_set_default_response((*C.GtkDialog)(dialog), index) + } + } + + defer C.gtk_widget_destroy((*C.GtkWidget)(dialog)) + return int(C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog)))) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (chan string, error) { + window := nilPointer + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + C.GTK_FILE_CHOOSER_ACTION_SAVE, + buttonText, + dialog.filters) + + return results, err +} + +func (w *linuxWebviewWindow) cut() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_CUT) +} + +func (w *linuxWebviewWindow) paste() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_PASTE) +} + +func (w *linuxWebviewWindow) copy() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_COPY) +} + +func (w *linuxWebviewWindow) selectAll() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_SELECT_ALL) +} + +func (w *linuxWebviewWindow) undo() { + //C.webkit_web_view_execute_editing_command(w.webview, C.WEBKIT_EDITING_COMMAND_UNDO) +} + +func (w *linuxWebviewWindow) redo() { +} + +func (w *linuxWebviewWindow) delete() { +} diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go new file mode 100644 index 000000000..c305e75b9 --- /dev/null +++ b/v3/pkg/application/linux_purego.go @@ -0,0 +1,1228 @@ +//go:build linux && purego + +package application + +import ( + "fmt" + "os" + "strings" + "unsafe" + + "github.com/ebitengine/purego" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type windowPointer uintptr +type identifier uint +type pointer uintptr + +// type GSList uintptr +type GSList struct { + data pointer + next *GSList +} + +type GSListPointer *GSList + +const ( + nilPointer pointer = 0 +) + +const ( + GSourceRemove int = 0 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121 + GdkHintMinSize = 1 << 1 + GdkHintMaxSize = 1 << 2 + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512 + GdkWindowStateIconified = 1 << 1 + GdkWindowStateMaximized = 1 << 2 + GdkWindowStateFullscreen = 1 << 4 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkmessagedialog.h#L87 + GtkButtonsNone int = 0 + GtkButtonsOk = 1 + GtkButtonsClose = 2 + GtkButtonsCancel = 3 + GtkButtonsYesNo = 4 + GtkButtonsOkCancel = 5 + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkdialog.h#L36 + GtkDialogModal = 1 << 0 + GtkDialogDestroyWithParent = 1 << 1 + GtkDialogUseHeaderBar = 1 << 2 // actions in header bar instead of action area + + GtkOrientationVertical = 1 + + // enum GtkMessageType + GtkMessageInfo = 0 + GtkMessageWarning = 1 + GtkMessageQuestion = 2 + GtkMessageError = 3 +) + +type GdkGeometry struct { + minWidth int32 + minHeight int32 + maxWidth int32 + maxHeight int32 + baseWidth int32 + baseHeight int32 + widthInc int32 + heightInc int32 + padding int32 + minAspect float64 + maxAspect float64 + GdkGravity int32 +} + +var ( + nilRadioGroup GSListPointer = nil + gtkSignalHandlers map[pointer]uint = map[pointer]uint{} + gtkSignalToMenuItem map[pointer]*MenuItem = map[pointer]*MenuItem{} + mainThreadId uint64 +) + +const ( + // TODO: map distro => so filename - with fallback? + gtk3 = "libgtk-3.so.0" + gtk4 = "libgtk-4.so.1" + webkit4 = "libwebkit2gtk-4.1.so.0" +) + +var ( + gtk uintptr + gtkVersion int + webkit uintptr + + // function references + gApplicationHold func(pointer) + gApplicationQuit func(pointer) + gApplicationName func() string + gApplicationRelease func(pointer) + gApplicationRun func(pointer, int, []string) int + gBytesNewStatic func(uintptr, int) uintptr + gBytesUnref func(uintptr) + gFree func(pointer) + gIdleAdd func(uintptr) + gObjectRefSink func(pointer) + gObjectUnref func(pointer) + gSignalConnectData func(pointer, string, uintptr, pointer, bool, int) int + gSignalConnectObject func(pointer, string, pointer, pointer, int) uint + gSignalHandlerBlock func(pointer, uint) + gSignalHandlerUnblock func(pointer, uint) + gThreadSelf func() uint64 + + // gdk functions + gdkDisplayGetMonitor func(pointer, int) pointer + gdkDisplayGetMonitorAtWindow func(pointer, pointer) pointer + gdkDisplayGetNMonitors func(pointer) int + gdkMonitorGetGeometry func(pointer, pointer) pointer + gdkMonitorGetScaleFactor func(pointer) int + gdkMonitorIsPrimary func(pointer) int + gdkPixbufNewFromBytes func(uintptr, int, int, int, int, int, int) pointer + gdkRgbaParse func(pointer, string) bool + gdkScreenGetRgbaVisual func(pointer) pointer + gdkScreenIsComposited func(pointer) int + gdkWindowGetState func(pointer) int + gdkWindowGetDisplay func(pointer) pointer + + // gtk functions + gtkApplicationNew func(string, uint) pointer + gtkApplicationGetActiveWindow func(pointer) pointer + gtkApplicationGetWindows func(pointer) *GList + gtkApplicationWindowNew func(pointer) pointer + gtkBoxNew func(int, int) pointer + gtkBoxPackStart func(pointer, pointer, int, int, int) + gtkCheckMenuItemGetActive func(pointer) int + gtkCheckMenuItemNewWithLabel func(string) pointer + gtkCheckMenuItemSetActive func(pointer, int) + gtkContainerAdd func(pointer, pointer) + gtkCSSProviderLoadFromData func(pointer, string, int, pointer) + gtkCSSProviderNew func() pointer + gtkDialogAddButton func(pointer, string, int) + gtkDialogGetContentArea func(pointer) pointer + gtkDialogRun func(pointer) int + gtkDialogSetDefaultResponse func(pointer, int) + gtkDragDestSet func(pointer, uint, pointer, uint, uint) + gtkFileChooserAddFilter func(pointer, pointer) + gtkFileChooserDialogNew func(string, pointer, int, string, int, string, int, pointer) pointer + gtkFileChooserGetFilenames func(pointer) *GSList + gtkFileChooserSetAction func(pointer, int) + gtkFileChooserSetCreateFolders func(pointer, bool) + gtkFileChooserSetCurrentFolder func(pointer, string) + gtkFileChooserSetSelectMultiple func(pointer, bool) + gtkFileChooserSetShowHidden func(pointer, bool) + gtkFileFilterAddPattern func(pointer, string) + gtkFileFilterNew func() pointer + gtkFileFilterSetName func(pointer, string) + gtkImageNewFromPixbuf func(pointer) pointer + gtkMenuBarNew func() pointer + gtkMenuItemNewWithLabel func(string) pointer + gtkMenuItemSetLabel func(pointer, string) + gtkMenuItemSetSubmenu func(pointer, pointer) + gtkMenuNew func() pointer + gtkMenuShellAppend func(pointer, pointer) + gtkMessageDialogNew func(pointer, int, int, int, string) pointer + gtkRadioMenuItemGetGroup func(pointer) GSListPointer + gtkRadioMenuItemNewWithLabel func(GSListPointer, string) pointer + gtkSeparatorMenuItemNew func() pointer + gtkStyleContextAddProvider func(pointer, pointer, int) + gtkTargetEntryFree func(pointer) + gtkTargetEntryNew func(string, int, uint) pointer + gtkWidgetDestroy func(pointer) + gtkWidgetGetDisplay func(pointer) pointer + gtkWidgetGetScreen func(pointer) pointer + gtkWidgetGetStyleContext func(pointer) pointer + gtkWidgetGetWindow func(pointer) pointer + gtkWidgetHide func(pointer) + gtkWidgetIsVisible func(pointer) bool + gtkWidgetShow func(pointer) + gtkWidgetShowAll func(pointer) + gtkWidgetSetAppPaintable func(pointer, int) + gtkWidgetSetName func(pointer, string) + gtkWidgetSetSensitive func(pointer, int) + gtkWidgetSetToolTipText func(pointer, string) + gtkWidgetSetVisual func(pointer, pointer) + gtkWindowClose func(pointer) + gtkWindowFullScreen func(pointer) + gtkWindowGetPosition func(pointer, *int, *int) bool + gtkWindowGetSize func(pointer, *int, *int) + gtkWindowHasToplevelFocus func(pointer) int + gtkWindowKeepAbove func(pointer, bool) + gtkWindowMaximize func(pointer) + gtkWindowMinimize func(pointer) + gtkWindowMove func(pointer, int, int) + gtkWindowPresent func(pointer) + gtkWindowResize func(pointer, int, int) + gtkWindowSetDecorated func(pointer, int) + gtkWindowSetGeometryHints func(pointer, pointer, pointer, int) + gtkWindowSetKeepAbove func(pointer, bool) + gtkWindowSetResizable func(pointer, bool) + gtkWindowSetTitle func(pointer, string) + gtkWindowUnfullscreen func(pointer) + gtkWindowUnmaximize func(pointer) + + // webkit + webkitNewWithUserContentManager func(pointer) pointer + webkitRegisterUriScheme func(pointer, string, pointer, int, int) + webkitSettingsGetEnableDeveloperExtras func(pointer) bool + webkitSettingsSetHardwareAccelerationPolicy func(pointer, int) + webkitSettingsSetEnableDeveloperExtras func(pointer, bool) + webkitSettingsSetUserAgentWithApplicationDetails func(pointer, string, string) + webkitUserContentManagerNew func() pointer + webkitUserContentManagerRegisterScriptMessageHandler func(pointer, string) + webkitWebContextGetDefault func() pointer + webkitWebViewEvaluateJS func(pointer, string, int, pointer, string, pointer, pointer, pointer) + webkitWebViewGetSettings func(pointer) pointer + webkitWebViewGetZoom func(pointer) float64 + webkitWebViewLoadAlternateHTML func(pointer, string, string, *string) + webkitWebViewLoadUri func(pointer, string) + webkitWebViewSetBackgroundColor func(pointer, pointer) + webkitWebViewSetSettings func(pointer, pointer) + webkitWebViewSetZoomLevel func(pointer, float64) +) + +func init() { + // needed for GTK4 to function + _ = os.Setenv("GDK_BACKEND", "x11") + var err error + + // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + // if err == nil { + // version = 4 + // return + // } + + // log.Println("Failed to open GTK4: Falling back to GTK3") + + gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + gtkVersion = 3 + + webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + + // Function registration + // GLib + purego.RegisterLibFunc(&gApplicationHold, gtk, "g_application_hold") + purego.RegisterLibFunc(&gApplicationName, gtk, "g_get_application_name") + purego.RegisterLibFunc(&gApplicationQuit, gtk, "g_application_quit") + purego.RegisterLibFunc(&gApplicationRelease, gtk, "g_application_release") + purego.RegisterLibFunc(&gApplicationRun, gtk, "g_application_run") + purego.RegisterLibFunc(&gBytesNewStatic, gtk, "g_bytes_new_static") + purego.RegisterLibFunc(&gBytesUnref, gtk, "g_bytes_unref") + purego.RegisterLibFunc(&gFree, gtk, "g_free") + purego.RegisterLibFunc(&gIdleAdd, gtk, "g_idle_add") + purego.RegisterLibFunc(&gObjectRefSink, gtk, "g_object_ref_sink") + purego.RegisterLibFunc(&gObjectUnref, gtk, "g_object_unref") + purego.RegisterLibFunc(&gSignalConnectData, gtk, "g_signal_connect_data") + purego.RegisterLibFunc(&gSignalConnectObject, gtk, "g_signal_connect_object") + purego.RegisterLibFunc(&gSignalHandlerBlock, gtk, "g_signal_handler_block") + purego.RegisterLibFunc(&gSignalHandlerUnblock, gtk, "g_signal_handler_unblock") + purego.RegisterLibFunc(&gThreadSelf, gtk, "g_thread_self") + + // GDK + purego.RegisterLibFunc(&gdkDisplayGetMonitor, gtk, "gdk_display_get_monitor") + purego.RegisterLibFunc(&gdkDisplayGetMonitorAtWindow, gtk, "gdk_display_get_monitor_at_window") + purego.RegisterLibFunc(&gdkDisplayGetNMonitors, gtk, "gdk_display_get_n_monitors") + purego.RegisterLibFunc(&gdkMonitorGetGeometry, gtk, "gdk_monitor_get_geometry") + purego.RegisterLibFunc(&gdkMonitorGetScaleFactor, gtk, "gdk_monitor_get_scale_factor") + purego.RegisterLibFunc(&gdkMonitorIsPrimary, gtk, "gdk_monitor_is_primary") + purego.RegisterLibFunc(&gdkPixbufNewFromBytes, gtk, "gdk_pixbuf_new_from_bytes") + purego.RegisterLibFunc(&gdkRgbaParse, gtk, "gdk_rgba_parse") + purego.RegisterLibFunc(&gdkScreenGetRgbaVisual, gtk, "gdk_screen_get_rgba_visual") + purego.RegisterLibFunc(&gdkScreenIsComposited, gtk, "gdk_screen_is_composited") + purego.RegisterLibFunc(&gdkWindowGetDisplay, gtk, "gdk_window_get_display") + purego.RegisterLibFunc(&gdkWindowGetState, gtk, "gdk_window_get_state") + + // GTK3 + purego.RegisterLibFunc(>kApplicationNew, gtk, "gtk_application_new") + purego.RegisterLibFunc(>kApplicationGetActiveWindow, gtk, "gtk_application_get_active_window") + purego.RegisterLibFunc(>kApplicationGetWindows, gtk, "gtk_application_get_windows") + purego.RegisterLibFunc(>kApplicationWindowNew, gtk, "gtk_application_window_new") + purego.RegisterLibFunc(>kBoxNew, gtk, "gtk_box_new") + purego.RegisterLibFunc(>kBoxPackStart, gtk, "gtk_box_pack_start") + purego.RegisterLibFunc(>kCheckMenuItemGetActive, gtk, "gtk_check_menu_item_get_active") + purego.RegisterLibFunc(>kCheckMenuItemNewWithLabel, gtk, "gtk_check_menu_item_new_with_label") + purego.RegisterLibFunc(>kCheckMenuItemSetActive, gtk, "gtk_check_menu_item_set_active") + purego.RegisterLibFunc(>kContainerAdd, gtk, "gtk_container_add") + purego.RegisterLibFunc(>kCSSProviderLoadFromData, gtk, "gtk_css_provider_load_from_data") + purego.RegisterLibFunc(>kDialogAddButton, gtk, "gtk_dialog_add_button") + purego.RegisterLibFunc(>kDialogGetContentArea, gtk, "gtk_dialog_get_content_area") + purego.RegisterLibFunc(>kDialogRun, gtk, "gtk_dialog_run") + purego.RegisterLibFunc(>kDialogSetDefaultResponse, gtk, "gtk_dialog_set_default_response") + purego.RegisterLibFunc(>kDragDestSet, gtk, "gtk_drag_dest_set") + purego.RegisterLibFunc(>kFileChooserAddFilter, gtk, "gtk_file_chooser_add_filter") + purego.RegisterLibFunc(>kFileChooserDialogNew, gtk, "gtk_file_chooser_dialog_new") + purego.RegisterLibFunc(>kFileChooserGetFilenames, gtk, "gtk_file_chooser_get_filenames") + purego.RegisterLibFunc(>kFileChooserSetAction, gtk, "gtk_file_chooser_set_action") + purego.RegisterLibFunc(>kFileChooserSetCreateFolders, gtk, "gtk_file_chooser_set_create_folders") + purego.RegisterLibFunc(>kFileChooserSetCurrentFolder, gtk, "gtk_file_chooser_set_current_folder") + purego.RegisterLibFunc(>kFileChooserSetSelectMultiple, gtk, "gtk_file_chooser_set_select_multiple") + purego.RegisterLibFunc(>kFileChooserSetShowHidden, gtk, "gtk_file_chooser_set_show_hidden") + purego.RegisterLibFunc(>kFileFilterAddPattern, gtk, "gtk_file_filter_add_pattern") + purego.RegisterLibFunc(>kFileFilterNew, gtk, "gtk_file_filter_new") + purego.RegisterLibFunc(>kFileFilterSetName, gtk, "gtk_file_filter_set_name") + purego.RegisterLibFunc(>kImageNewFromPixbuf, gtk, "gtk_image_new_from_pixbuf") + purego.RegisterLibFunc(>kMenuItemSetLabel, gtk, "gtk_menu_item_set_label") + purego.RegisterLibFunc(>kMenuBarNew, gtk, "gtk_menu_bar_new") + purego.RegisterLibFunc(>kMenuItemNewWithLabel, gtk, "gtk_menu_item_new_with_label") + purego.RegisterLibFunc(>kMenuItemSetSubmenu, gtk, "gtk_menu_item_set_submenu") + purego.RegisterLibFunc(>kMenuNew, gtk, "gtk_menu_new") + purego.RegisterLibFunc(>kMenuShellAppend, gtk, "gtk_menu_shell_append") + purego.RegisterLibFunc(>kMessageDialogNew, gtk, "gtk_message_dialog_new") + purego.RegisterLibFunc(>kRadioMenuItemGetGroup, gtk, "gtk_radio_menu_item_get_group") + purego.RegisterLibFunc(>kRadioMenuItemNewWithLabel, gtk, "gtk_radio_menu_item_new_with_label") + purego.RegisterLibFunc(>kSeparatorMenuItemNew, gtk, "gtk_separator_menu_item_new") + purego.RegisterLibFunc(>kStyleContextAddProvider, gtk, "gtk_style_context_add_provider") + purego.RegisterLibFunc(>kTargetEntryFree, gtk, "gtk_target_entry_free") + purego.RegisterLibFunc(>kTargetEntryNew, gtk, "gtk_target_entry_new") + purego.RegisterLibFunc(>kWidgetDestroy, gtk, "gtk_widget_destroy") + purego.RegisterLibFunc(>kWidgetGetDisplay, gtk, "gtk_widget_get_display") + purego.RegisterLibFunc(>kWidgetGetScreen, gtk, "gtk_widget_get_screen") + purego.RegisterLibFunc(>kWidgetGetStyleContext, gtk, "gtk_widget_get_style_context") + purego.RegisterLibFunc(>kWidgetGetWindow, gtk, "gtk_widget_get_window") + purego.RegisterLibFunc(>kWidgetHide, gtk, "gtk_widget_hide") + purego.RegisterLibFunc(>kWidgetIsVisible, gtk, "gtk_widget_is_visible") + purego.RegisterLibFunc(>kWidgetSetAppPaintable, gtk, "gtk_widget_set_app_paintable") + purego.RegisterLibFunc(>kWidgetSetName, gtk, "gtk_widget_set_name") + purego.RegisterLibFunc(>kWidgetSetSensitive, gtk, "gtk_widget_set_sensitive") + purego.RegisterLibFunc(>kWidgetSetToolTipText, gtk, "gtk_widget_set_tooltip_text") + purego.RegisterLibFunc(>kWidgetSetVisual, gtk, "gtk_widget_set_visual") + purego.RegisterLibFunc(>kWidgetShow, gtk, "gtk_widget_show") + purego.RegisterLibFunc(>kWidgetShowAll, gtk, "gtk_widget_show_all") + purego.RegisterLibFunc(>kWindowFullScreen, gtk, "gtk_window_fullscreen") + purego.RegisterLibFunc(>kWindowClose, gtk, "gtk_window_close") + purego.RegisterLibFunc(>kWindowGetPosition, gtk, "gtk_window_get_position") + purego.RegisterLibFunc(>kWindowGetSize, gtk, "gtk_window_get_size") + purego.RegisterLibFunc(>kWindowMaximize, gtk, "gtk_window_maximize") + purego.RegisterLibFunc(>kWindowMove, gtk, "gtk_window_move") + purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_present") + //purego.RegisterLibFunc(>kWindowPresent, gtk, "gtk_window_unminimize") // gtk4 + purego.RegisterLibFunc(>kWindowHasToplevelFocus, gtk, "gtk_window_has_toplevel_focus") + purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_iconify") // gtk3 + // purego.RegisterLibFunc(>kWindowMinimize, gtk, "gtk_window_minimize") // gtk4 + purego.RegisterLibFunc(>kWindowResize, gtk, "gtk_window_resize") + purego.RegisterLibFunc(>kWindowSetGeometryHints, gtk, "gtk_window_set_geometry_hints") + purego.RegisterLibFunc(>kWindowSetDecorated, gtk, "gtk_window_set_decorated") + purego.RegisterLibFunc(>kWindowKeepAbove, gtk, "gtk_window_set_keep_above") + purego.RegisterLibFunc(>kWindowSetResizable, gtk, "gtk_window_set_resizable") + purego.RegisterLibFunc(>kWindowSetTitle, gtk, "gtk_window_set_title") + purego.RegisterLibFunc(>kWindowUnfullscreen, gtk, "gtk_window_unfullscreen") + purego.RegisterLibFunc(>kWindowUnmaximize, gtk, "gtk_window_unmaximize") + + // webkit + purego.RegisterLibFunc(&webkitNewWithUserContentManager, webkit, "webkit_web_view_new_with_user_content_manager") + purego.RegisterLibFunc(&webkitRegisterUriScheme, webkit, "webkit_web_context_register_uri_scheme") + purego.RegisterLibFunc(&webkitSettingsGetEnableDeveloperExtras, webkit, "webkit_settings_get_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetEnableDeveloperExtras, webkit, "webkit_settings_set_enable_developer_extras") + purego.RegisterLibFunc(&webkitSettingsSetHardwareAccelerationPolicy, webkit, "webkit_settings_set_hardware_acceleration_policy") + purego.RegisterLibFunc(&webkitSettingsSetUserAgentWithApplicationDetails, webkit, "webkit_settings_set_user_agent_with_application_details") + purego.RegisterLibFunc(&webkitUserContentManagerNew, webkit, "webkit_user_content_manager_new") + purego.RegisterLibFunc(&webkitUserContentManagerRegisterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler") + purego.RegisterLibFunc(&webkitWebContextGetDefault, webkit, "webkit_web_context_get_default") + purego.RegisterLibFunc(&webkitWebViewEvaluateJS, webkit, "webkit_web_view_evaluate_javascript") + purego.RegisterLibFunc(&webkitWebViewGetSettings, webkit, "webkit_web_view_get_settings") + purego.RegisterLibFunc(&webkitWebViewGetZoom, webkit, "webkit_web_view_get_zoom_level") + purego.RegisterLibFunc(&webkitWebViewLoadAlternateHTML, webkit, "webkit_web_view_load_alternate_html") + purego.RegisterLibFunc(&webkitWebViewLoadUri, webkit, "webkit_web_view_load_uri") + purego.RegisterLibFunc(&webkitWebViewSetBackgroundColor, webkit, "webkit_web_view_set_background_color") + purego.RegisterLibFunc(&webkitWebViewSetSettings, webkit, "webkit_web_view_set_settings") + purego.RegisterLibFunc(&webkitWebViewSetZoomLevel, webkit, "webkit_web_view_set_zoom_level") +} + +// mainthread stuff +func dispatchOnMainThread(id uint) { + gIdleAdd(purego.NewCallback(func(pointer) int { + executeOnMainThread(id) + return GSourceRemove + })) +} + +// implementation below +func appName() string { + return gApplicationName() +} + +func appNew(name string) pointer { + GApplicationDefaultFlags := uint(0) + + name = strings.ToLower(name) + if name == "" { + name = "undefined" + } + identifier := fmt.Sprintf("org.wails.%s", strings.Replace(name, " ", "-", -1)) + + return pointer(gtkApplicationNew(identifier, GApplicationDefaultFlags)) +} + +func appRun(application pointer) error { + mainThreadId = gThreadSelf() + fmt.Println("linux_purego: appRun threadID", mainThreadId) + + app := pointer(application) + activate := func() { + // TODO: Do we care? + fmt.Println("linux.activated!") + gApplicationHold(app) // allow running without a window + } + gSignalConnectData( + application, + "activate", + purego.NewCallback(activate), + app, + false, + 0) + + status := gApplicationRun(app, 0, nil) + gApplicationRelease(app) + gObjectUnref(app) + + var err error + if status != 0 { + err = fmt.Errorf("exit code: %d", status) + } + return err +} + +func appDestroy(application pointer) { + gApplicationQuit(pointer(application)) +} + +func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint { + // TODO: Add extra metadata to window and use it! + window := gtkApplicationGetActiveWindow(pointer(application)) + identifier, ok := windows[windowPointer(window)] + if ok { + return identifier + } + // FIXME: Should we panic here if not found? + return uint(1) +} + +type GList struct { + data pointer + next *GList + prev *GList +} + +func getWindows(application pointer) []pointer { + result := []pointer{} + windows := gtkApplicationGetWindows(pointer(application)) + // FIXME: Need to make a struct here to deal with response data + for { + result = append(result, pointer(windows.data)) + windows = windows.next + if windows == nil { + return result + } + } +} + +func hideAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetHide(window) + } +} + +func showAllWindows(application pointer) { + for _, window := range getWindows(application) { + gtkWidgetShowAll(window) + } +} + +// Menu +func menuAddSeparator(menu *Menu) { + gtkMenuShellAppend( + pointer((menu.impl).(*linuxMenu).native), + gtkSeparatorMenuItemNew()) +} + +func menuAppend(parent *Menu, menu *MenuItem) { + // TODO: override this with the GTK4 version if needed - possibly rename to imply it's an alias + gtkMenuShellAppend( + pointer((parent.impl).(*linuxMenu).native), + pointer((menu.impl).(*linuxMenuItem).native)) + + /* gtk4 + C.gtk_menu_item_set_submenu( + (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), + (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), + ) + */ +} + +func menuBarNew() pointer { + return pointer(gtkMenuBarNew()) +} + +func menuNew() pointer { + return pointer(gtkMenuNew()) +} + +func menuSetSubmenu(item *MenuItem, menu *Menu) { + // FIXME: How is this different than `menuAppend` above? + gtkMenuItemSetSubmenu( + pointer((item.impl).(*linuxMenuItem).native), + pointer((menu.impl).(*linuxMenu).native)) +} + +func menuGetRadioGroup(item *linuxMenuItem) *GSList { + return (*GSList)(gtkRadioMenuItemGetGroup(pointer(item.native))) +} + +func attachMenuHandler(item *MenuItem) { + handleClick := func() { + item := item + switch item.itemType { + case text, checkbox: + menuItemClicked <- item.id + case radio: + menuItem := (item.impl).(*linuxMenuItem) + if menuItem.isChecked() { + menuItemClicked <- item.id + } + } + } + + impl := (item.impl).(*linuxMenuItem) + widget := impl.native + flags := 0 + handlerId := gSignalConnectObject( + pointer(widget), + "activate", + pointer(purego.NewCallback(handleClick)), + pointer(widget), + flags) + impl.handlerId = uint(handlerId) +} + +// menuItem +func menuItemChecked(widget pointer) bool { + if gtkCheckMenuItemGetActive(widget) == 1 { + return true + } + return false +} + +func menuItemNew(label string) pointer { + return pointer(gtkMenuItemNewWithLabel(label)) +} + +func menuCheckItemNew(label string) pointer { + return pointer(gtkCheckMenuItemNewWithLabel(label)) +} + +func menuItemSetChecked(widget pointer, checked bool) { + value := 0 + if checked { + value = 1 + } + gtkCheckMenuItemSetActive(pointer(widget), value) +} + +func menuItemSetDisabled(widget pointer, disabled bool) { + value := 1 + if disabled { + value = 0 + } + gtkWidgetSetSensitive(widget, value) +} + +func menuItemSetLabel(widget pointer, label string) { + gtkMenuItemSetLabel( + pointer(widget), + label) +} + +func menuItemSetToolTip(widget pointer, tooltip string) { + gtkWidgetSetToolTipText( + pointer(widget), + tooltip) +} + +func menuItemSignalBlock(widget pointer, handlerId uint, block bool) { + if block { + gSignalHandlerBlock(widget, handlerId) + } else { + gSignalHandlerUnblock(widget, handlerId) + } +} + +func menuRadioItemNew(group GSListPointer, label string) pointer { + return pointer(gtkRadioMenuItemNewWithLabel(group, label)) +} + +// screen related + +func getScreenByIndex(display pointer, index int) *Screen { + monitor := gdkDisplayGetMonitor(display, index) + // TODO: Do we need to update Screen to contain current info? + // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) + + geometry := struct { + x int32 + y int32 + width int32 + height int32 + }{} + result := pointer(unsafe.Pointer(&geometry)) + gdkMonitorGetGeometry(monitor, result) + + primary := false + if gdkMonitorIsPrimary(monitor) == 1 { + primary = true + } + + return &Screen{ + IsPrimary: primary, + ScaleFactor: 1.0, + X: int(geometry.x), + Y: int(geometry.y), + Size: Size{ + Height: int(geometry.height), + Width: int(geometry.width), + }, + } +} + +func getScreens(app pointer) ([]*Screen, error) { + var screens []*Screen + window := gtkApplicationGetActiveWindow(app) + display := gdkWindowGetDisplay(window) + count := gdkDisplayGetNMonitors(display) + for i := 0; i < int(count); i++ { + screens = append(screens, getScreenByIndex(display, i)) + } + return screens, nil +} + +// widgets +func widgetSetSensitive(widget pointer, enabled bool) { + value := 0 + if enabled { + value = 1 + } + gtkWidgetSetSensitive(widget, value) +} + +func widgetSetVisible(widget pointer, hidden bool) { + if hidden { + gtkWidgetHide(widget) + } else { + gtkWidgetShow(widget) + } +} + +// window related functions +func windowClose(window pointer) { + gtkWindowClose(window) +} + +func windowEnableDND(id uint, webview pointer) { + targetentry := gtkTargetEntryNew("text/uri-list", 0, id) + defer gtkTargetEntryFree(targetentry) + + GtkDestDefaultDrop := uint(0) + GdkActionCopy := uint(0) //? + gtkDragDestSet(webview, GtkDestDefaultDrop, targetentry, 1, GdkActionCopy) + + // FIXME: enable and process + /* gSignalConnectData(webview, + "drag-data-received", + purego.NewCallback(onDragNDrop), + 0, + false, + 0)*/ +} + +func windowExecJS(webview pointer, js string) { + webkitWebViewEvaluateJS( + webview, + js, + len(js), + 0, + "", + 0, + 0, + 0) +} + +func windowDestroy(window pointer) { + // Should this truly 'destroy' ? + gtkWindowClose(window) +} + +func windowFullscreen(window pointer) { + gtkWindowFullScreen(window) +} + +func windowGetPosition(window pointer) (int, int) { + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowGetCurrentMonitor(window pointer) pointer { + // Get the monitor that the window is currently on + display := gtkWidgetGetDisplay(window) + window = gtkWidgetGetWindow(window) + if window == 0 { + return 0 + } + return gdkDisplayGetMonitorAtWindow(display, window) +} + +func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scaleFactor int) { + monitor := windowGetCurrentMonitor(window) + if monitor == 0 { + return -1, -1, -1, -1, 1 + } + + result := struct { + x int32 + y int32 + width int32 + height int32 + }{} + gdkMonitorGetGeometry(monitor, pointer(unsafe.Pointer(&result))) + return int(result.x), int(result.y), int(result.width), int(result.height), gdkMonitorGetScaleFactor(monitor) +} + +func windowGetRelativePosition(window pointer) (int, int) { + absX, absY := windowGetPosition(window) + x, y, _, _, _ := windowGetCurrentMonitorGeometry(window) + + relX := absX - x + relY := absY - y + + // TODO: Scale based on DPI + return relX, relY +} + +func windowGetSize(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var width, height int + gtkWindowGetSize(window, &width, &height) + return width, height +} + +func windowGetPosition(window pointer) (int, int) { + // TODO: dispatchOnMainThread? + var x, y int + gtkWindowGetPosition(window, &x, &y) + return x, y +} + +func windowHide(window pointer) { + gtkWidgetHide(window) +} + +func windowIsFocused(window pointer) bool { + return gtkWindowHasToplevelFocus(window) == 1 +} + +func windowIsFullscreen(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateFullscreen > 0 +} + +func windowIsMaximized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateMaximized > 0 && state&GdkWindowStateFullscreen == 0 +} + +func windowIsMinimized(window pointer) bool { + gdkwindow := gtkWidgetGetWindow(window) + state := gdkWindowGetState(gdkwindow) + return state&GdkWindowStateIconified > 0 +} + +func windowIsVisible(window pointer) bool { + // TODO: validate this works.. (used a `bool` in the registration) + return gtkWidgetIsVisible(window) +} + +func windowMaximize(window pointer) { + gtkWindowMaximize(window) +} + +func windowMinimize(window pointer) { + gtkWindowMinimize(window) +} + +func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (pointer, pointer, pointer) { + window := gtkApplicationWindowNew(application) + gObjectRefSink(window) + webview := windowNewWebview(windowId, gpuPolicy) + vbox := gtkBoxNew(GtkOrientationVertical, 0) + gtkContainerAdd(window, vbox) + gtkWidgetSetName(vbox, "webview-box") + + if menu != 0 { + gtkBoxPackStart(vbox, menu, 0, 0, 0) + } + gtkBoxPackStart(vbox, webview, 1, 1, 0) + return pointer(window), pointer(webview), pointer(vbox) +} + +func windowNewWebview(parentId uint, gpuPolicy int) pointer { + manager := webkitUserContentManagerNew() + webkitUserContentManagerRegisterScriptMessageHandler(manager, "external") + wv := webkitNewWithUserContentManager(manager) + if !registered { + webkitRegisterUriScheme( + webkitWebContextGetDefault(), + "wails", + pointer(purego.NewCallback(func(request uintptr) { + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(request), + windowId: parentId, + windowName: func() string { + if window, ok := globalApplication.Window.GetByID(parentId); ok { + return window.Name() + } + return "" + }(), + } + })), + 0, + 0, + ) + registered = true + } + + settings := webkitWebViewGetSettings(wv) + webkitSettingsSetUserAgentWithApplicationDetails( + settings, + "wails.io", + "") + webkitSettingsSetHardwareAccelerationPolicy(settings, gpuPolicy) + webkitWebViewSetSettings(wv, settings) + return wv +} + +func windowPresent(window pointer) { + gtkWindowPresent(pointer(window)) +} + +func windowReload(webview pointer, address string) { + webkitWebViewLoadUri(pointer(webview), address) +} + +func windowResize(window pointer, width, height int) { + gtkWindowResize(window, width, height) +} + +func windowShow(window pointer) { + gtkWidgetShowAll(pointer(window)) +} + +func windowSetBackgroundColour(vbox, webview pointer, colour RGBA) { + const GtkStyleProviderPriorityUser = 800 + + // FIXME: Use a struct! + rgba := make([]byte, 4*8) // C.sizeof_GdkRGBA == 32 + rgbaPointer := pointer(unsafe.Pointer(&rgba[0])) + if !gdkRgbaParse( + rgbaPointer, + fmt.Sprintf("rgba(%v,%v,%v,%v)", + colour.Red, + colour.Green, + colour.Blue, + float32(colour.Alpha)/255.0, + )) { + return + } + webkitWebViewSetBackgroundColor(pointer(webview), rgbaPointer) + + colour.Alpha = 255 + css := fmt.Sprintf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", colour.Red, colour.Green, colour.Blue, float32(colour.Alpha)/255.0) + provider := gtkCSSProviderNew() + defer gObjectUnref(provider) + gtkStyleContextAddProvider( + gtkWidgetGetStyleContext(vbox), + provider, + GtkStyleProviderPriorityUser, + ) + gtkCSSProviderLoadFromData(provider, css, -1, 0) +} + +func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) { + size := GdkGeometry{ + minWidth: int32(minWidth), + minHeight: int32(minHeight), + maxWidth: int32(maxWidth), + maxHeight: int32(maxHeight), + } + gtkWindowSetGeometryHints( + pointer(window), + pointer(0), + pointer(unsafe.Pointer(&size)), + GdkHintMinSize|GdkHintMaxSize) +} + +func windowSetFrameless(window pointer, frameless bool) { + decorated := 1 + if frameless { + decorated = 0 + } + gtkWindowSetDecorated(pointer(window), decorated) + + // TODO: Deal with transparency for the titlebar if possible when !frameless + // Perhaps we just make it undecorated and add a menu bar inside? +} + +// TODO: confirm this is working properly +func windowSetHTML(webview pointer, html string) { + webkitWebViewLoadAlternateHTML(webview, html, "wails://", nil) +} + +func windowSetKeepAbove(window pointer, alwaysOnTop bool) { + gtkWindowKeepAbove(window, alwaysOnTop) +} + +func windowSetResizable(window pointer, resizable bool) { + // FIXME: Does this work? + gtkWindowSetResizable( + pointer(window), + resizable, + ) +} + +func windowSetTitle(window pointer, title string) { + gtkWindowSetTitle(pointer(window), title) +} + +func windowSetTransparent(window pointer) { + screen := gtkWidgetGetScreen(pointer(window)) + visual := gdkScreenGetRgbaVisual(screen) + if visual == 0 { + return + } + if gdkScreenIsComposited(screen) == 1 { + gtkWidgetSetAppPaintable(pointer(window), 1) + gtkWidgetSetVisual(pointer(window), visual) + } +} + +func windowSetURL(webview pointer, uri string) { + webkitWebViewLoadUri(webview, uri) +} + +func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) { + handleDelete := purego.NewCallback(func(pointer) { + emit(events.Common.WindowClosing) + }) + gSignalConnectData(window, "delete-event", handleDelete, 0, false, 0) + + /* + event = C.CString("load-changed") + defer C.free(unsafe.Pointer(event)) + C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id)) + */ + + // TODO: Handle mouse button / drag events + /* id := C.uint(windowId) + event = C.CString("button-press-event") + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, unsafe.Pointer(&id)) + C.free(unsafe.Pointer(event)) + event = C.CString("button-release-event") + defer C.free(unsafe.Pointer(event)) + C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, onButtonEvent, unsafe.Pointer(&id)) + */ +} + +func windowOpenDevTools(webview pointer) { + settings := webkitWebViewGetSettings(pointer(webview)) + webkitSettingsSetEnableDeveloperExtras( + settings, + !webkitSettingsGetEnableDeveloperExtras(settings)) +} + +func windowUnfullscreen(window pointer) { + gtkWindowUnfullscreen(window) +} + +func windowUnmaximize(window pointer) { + gtkWindowUnmaximize(window) +} + +func windowZoom(webview pointer) float64 { + return webkitWebViewGetZoom(webview) +} + +func windowZoomIn(webview pointer) { + ZoomInFactor := 1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomInFactor) +} +func windowZoomOut(webview pointer) { + ZoomOutFactor := -1.10 + windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor) +} + +func windowZoomSet(webview pointer, zoom float64) { + if zoom < 1.0 { // 1.0 is the smallest allowable + zoom = 1.0 + } + webkitWebViewSetZoomLevel(webview, zoom) +} + +func windowMove(window pointer, x, y int) { + gtkWindowMove(window, x, y) +} + +func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden bool, currentFolder, title string, action int, acceptLabel string, filters []FileFilter) ([]string, error) { + GtkResponseCancel := 0 + GtkResponseAccept := 1 + + fc := gtkFileChooserDialogNew( + title, + window, + action, + "_Cancel", + GtkResponseCancel, + acceptLabel, + GtkResponseAccept, + 0) + + gtkFileChooserSetAction(fc, action) + + gtkFilters := []pointer{} + for _, filter := range filters { + f := gtkFileFilterNew() + gtkFileFilterSetName(f, filter.DisplayName) + gtkFileFilterAddPattern(f, filter.Pattern) + gtkFileChooserAddFilter(fc, f) + gtkFilters = append(gtkFilters, f) + } + gtkFileChooserSetSelectMultiple(fc, allowMultiple) + gtkFileChooserSetCreateFolders(fc, createFolders) + gtkFileChooserSetShowHidden(fc, showHidden) + + if currentFolder != "" { + gtkFileChooserSetCurrentFolder(fc, currentFolder) + } + + buildStringAndFree := func(s pointer) string { + bytes := []byte{} + p := unsafe.Pointer(s) + for { + val := *(*byte)(p) + if val == 0 { // this is the null terminator + break + } + bytes = append(bytes, val) + p = unsafe.Add(p, 1) + } + gFree(s) // so we don't have to iterate a second time + return string(bytes) + } + + response := gtkDialogRun(fc) + selections := []string{} + if response == GtkResponseAccept { + filenames := gtkFileChooserGetFilenames(fc) + iter := filenames + count := 0 + for { + selections = append(selections, buildStringAndFree(iter.data)) + iter = iter.next + if iter == nil || count == 1024 { + break + } + count++ + } + } + defer gtkWidgetDestroy(fc) + return selections, nil +} + +// dialog related +func runOpenFileDialog(dialog *OpenFileDialogStruct) ([]string, error) { + const GtkFileChooserActionOpen = 0 + const GtkFileChooserActionSelectFolder = 2 + + var action int + + if dialog.canChooseDirectories { + action = GtkFileChooserActionSelectFolder + } else { + action = GtkFileChooserActionOpen + } + + window := pointer(0) + if dialog.window != nil { + window = (dialog.window.(*WebviewWindow).impl).(*linuxWebviewWindow).window + } + + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Open" + } + + return runChooserDialog( + window, + dialog.allowsMultipleSelection, + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionOpen, + buttonText, + dialog.filters) +} + +func runQuestionDialog(parent pointer, options *MessageDialog) int { + dType, ok := map[DialogType]int{ + InfoDialogType: GtkMessageInfo, + WarningDialogType: GtkMessageWarning, + QuestionDialogType: GtkMessageQuestion, + }[options.DialogType] + if !ok { + // FIXME: Add logging here! + dType = GtkMessageInfo + } + buttonMask := GtkButtonsOk + if len(options.Buttons) > 0 { + buttonMask = GtkButtonsNone + } + + dialog := gtkMessageDialogNew( + pointer(parent), + GtkDialogModal|GtkDialogDestroyWithParent, + dType, + buttonMask, + options.Message) + + if options.Title != "" { + gtkWindowSetTitle(dialog, options.Title) + } + + GdkColorspaceRGB := 0 + + if img, err := pngToImage(options.Icon); err == nil { + gbytes := gBytesNewStatic(uintptr(unsafe.Pointer(&img.Pix[0])), len(img.Pix)) + + defer gBytesUnref(gbytes) + pixBuf := gdkPixbufNewFromBytes( + gbytes, + GdkColorspaceRGB, + 1, // has_alpha + 8, + img.Bounds().Dx(), + img.Bounds().Dy(), + img.Stride, + ) + image := gtkImageNewFromPixbuf(pixBuf) + widgetSetVisible(image, false) + contentArea := gtkDialogGetContentArea(dialog) + gtkContainerAdd(contentArea, image) + } + + for i, button := range options.Buttons { + gtkDialogAddButton( + dialog, + button.Label, + i, + ) + if button.IsDefault { + gtkDialogSetDefaultResponse(dialog, i) + } + } + defer gtkWidgetDestroy(dialog) + return gtkDialogRun(dialog) +} + +func runSaveFileDialog(dialog *SaveFileDialogStruct) (string, error) { + const GtkFileChooserActionSave = 1 + + window := pointer(0) + buttonText := dialog.buttonText + if buttonText == "" { + buttonText = "_Save" + } + results, err := runChooserDialog( + window, + false, // multiple selection + dialog.canCreateDirectories, + dialog.showHiddenFiles, + dialog.directory, + dialog.title, + GtkFileChooserActionSave, + buttonText, + dialog.filters) + + if err != nil || len(results) == 0 { + return "", err + } + + return results[0], nil +} + +func isOnMainThread() bool { + return mainThreadId == gThreadSelf() +} + +// linuxWebviewWindow show/hide methods for purego implementation +func (w *linuxWebviewWindow) windowShow() { + if w.window == 0 { + return + } + windowShow(w.window) +} + +func (w *linuxWebviewWindow) windowHide() { + if w.window == 0 { + return + } + windowHide(w.window) +} diff --git a/v3/pkg/application/logger_dev.go b/v3/pkg/application/logger_dev.go new file mode 100644 index 000000000..e84f49490 --- /dev/null +++ b/v3/pkg/application/logger_dev.go @@ -0,0 +1,20 @@ +//go:build !windows && !production + +package application + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" + "github.com/mattn/go-isatty" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(tint.NewHandler(os.Stderr, &tint.Options{ + TimeFormat: time.Kitchen, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/logger_dev_windows.go b/v3/pkg/application/logger_dev_windows.go new file mode 100644 index 000000000..20d20c376 --- /dev/null +++ b/v3/pkg/application/logger_dev_windows.go @@ -0,0 +1,21 @@ +//go:build windows && !production + +package application + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(tint.NewHandler(colorable.NewColorable(os.Stderr), &tint.Options{ + TimeFormat: time.StampMilli, + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + Level: level, + })) +} diff --git a/v3/pkg/application/logger_prod.go b/v3/pkg/application/logger_prod.go new file mode 100644 index 000000000..a748611ce --- /dev/null +++ b/v3/pkg/application/logger_prod.go @@ -0,0 +1,12 @@ +//go:build production + +package application + +import ( + "io" + "log/slog" +) + +func DefaultLogger(level slog.Leveler) *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} diff --git a/v3/pkg/application/mainthread.go b/v3/pkg/application/mainthread.go new file mode 100644 index 000000000..6eb40ba9d --- /dev/null +++ b/v3/pkg/application/mainthread.go @@ -0,0 +1,87 @@ +package application + +import ( + "sync" +) + +var mainThreadFunctionStore = make(map[uint]func()) +var mainThreadFunctionStoreLock sync.RWMutex + +func generateFunctionStoreID() uint { + startID := 0 + for { + if _, ok := mainThreadFunctionStore[uint(startID)]; !ok { + return uint(startID) + } + startID++ + if startID == 0 { + Fatal("Too many functions have been dispatched to the main thread") + } + } +} + +func InvokeSync(fn func()) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + fn() + wg.Done() + }) + wg.Wait() +} + +func InvokeSyncWithResult[T any](fn func() T) (res T) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res = fn() + wg.Done() + }) + wg.Wait() + return res +} + +func InvokeSyncWithError(fn func() error) (err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + err = fn() + wg.Done() + }) + wg.Wait() + return +} + +func InvokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res, err = fn() + wg.Done() + }) + wg.Wait() + return res, err +} + +func InvokeSyncWithResultAndOther[T any, U any](fn func() (T, U)) (res T, other U) { + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + res, other = fn() + wg.Done() + }) + wg.Wait() + return res, other +} + +func InvokeAsync(fn func()) { + globalApplication.dispatchOnMainThread(func() { + defer handlePanic() + fn() + }) +} diff --git a/v3/pkg/application/mainthread_darwin.go b/v3/pkg/application/mainthread_darwin.go new file mode 100644 index 000000000..70e4d70b1 --- /dev/null +++ b/v3/pkg/application/mainthread_darwin.go @@ -0,0 +1,45 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "Cocoa/Cocoa.h" + +extern void dispatchOnMainThreadCallback(unsigned int); + +static void dispatchOnMainThread(unsigned int id) { + dispatch_async(dispatch_get_main_queue(), ^{ + dispatchOnMainThreadCallback(id); + }); +} + +static bool onMainThread() { + return [NSThread isMainThread]; +} + +*/ +import "C" + +func (m *macosApp) isOnMainThread() bool { + return bool(C.onMainThread()) +} + +func (m *macosApp) dispatchOnMainThread(id uint) { + C.dispatchOnMainThread(C.uint(id)) +} + +//export dispatchOnMainThreadCallback +func dispatchOnMainThreadCallback(callbackID C.uint) { + mainThreadFunctionStoreLock.RLock() + id := uint(callbackID) + fn := mainThreadFunctionStore[id] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", id) + } + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_linux.go b/v3/pkg/application/mainthread_linux.go new file mode 100644 index 000000000..a718688bc --- /dev/null +++ b/v3/pkg/application/mainthread_linux.go @@ -0,0 +1,18 @@ +//go:build linux + +package application + +func (a *linuxApp) dispatchOnMainThread(id uint) { + dispatchOnMainThread(id) +} + +func executeOnMainThread(callbackID uint) { + mainThreadFunctionStoreLock.RLock() + fn := mainThreadFunctionStore[callbackID] + if fn == nil { + Fatal("dispatchCallback called with invalid id: %v", callbackID) + } + delete(mainThreadFunctionStore, callbackID) + mainThreadFunctionStoreLock.RUnlock() + fn() +} diff --git a/v3/pkg/application/mainthread_windows.go b/v3/pkg/application/mainthread_windows.go new file mode 100644 index 000000000..ddee96339 --- /dev/null +++ b/v3/pkg/application/mainthread_windows.go @@ -0,0 +1,127 @@ +//go:build windows + +package application + +import ( + "runtime" + "sort" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var ( + wmInvokeCallback uint32 +) + +func init() { + wmInvokeCallback = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("WailsV0.InvokeCallback")) +} + +// initMainLoop must be called with the same OSThread that is used to call runMainLoop() later. +func (m *windowsApp) initMainLoop() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.mainThreadWindowHWND != 0 { + panic("initMainLoop was already called") + } + + // We need a hidden window so we can PostMessage to it, if we don't use PostMessage for dispatching to a HWND + // messages might get lost if a modal inner loop is being run. + // We had this once in V2: https://github.com/wailsapp/wails/issues/969 + // See: https://devblogs.microsoft.com/oldnewthing/20050426-18/?p=35783 + // See also: https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues#creating-a-message-loop + // > Because the system directs messages to individual windows in an application, a thread must create at least one window before starting its message loop. + m.mainThreadWindowHWND = w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(m.parent.options.Windows.WndClass), + w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"), + w32.WS_DISABLED, + w32.CW_USEDEFAULT, + w32.CW_USEDEFAULT, + 0, + 0, + 0, + 0, + w32.GetModuleHandle(""), + nil) + + m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND) +} + +func (m *windowsApp) runMainLoop() int { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on") + } + + msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{}))))) + defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(msg))) + + for w32.GetMessage(msg, 0, 0, 0) != 0 { + w32.TranslateMessage(msg) + w32.DispatchMessage(msg) + } + + return int(msg.WParam) +} + +func (m *windowsApp) dispatchOnMainThread(id uint) { + mainThreadHWND := m.mainThreadWindowHWND + if mainThreadHWND == 0 { + panic("initMainLoop was not called") + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0) + } else { + mainThreadFunctionStoreLock.Lock() + fn := mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + mainThreadFunctionStoreLock.Unlock() + + if fn == nil { + Fatal("dispatchOnMainThread called with invalid id: %v", id) + } + fn() + } +} + +func (m *windowsApp) invokeRequired() bool { + mainThreadID := m.mainThreadID + if mainThreadID == 0 { + panic("initMainLoop was not called") + } + + return mainThreadID != w32.GetCurrentThreadId() +} + +func (m *windowsApp) invokeCallback(wParam, lParam uintptr) { + // TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings... + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if m.invokeRequired() { + panic("invokeCallback must always be called on the MainOSThread") + } + + mainThreadFunctionStoreLock.Lock() + fnIDs := make([]uint, 0, len(mainThreadFunctionStore)) + for id := range mainThreadFunctionStore { + fnIDs = append(fnIDs, id) + } + sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] }) + + fns := make([]func(), len(fnIDs)) + for i, id := range fnIDs { + fns[i] = mainThreadFunctionStore[id] + delete(mainThreadFunctionStore, id) + } + mainThreadFunctionStoreLock.Unlock() + + for _, fn := range fns { + fn() + } +} diff --git a/v3/pkg/application/menu.go b/v3/pkg/application/menu.go new file mode 100644 index 000000000..96a971da1 --- /dev/null +++ b/v3/pkg/application/menu.go @@ -0,0 +1,233 @@ +package application + +type menuImpl interface { + update() +} + +type ContextMenu struct { + *Menu + name string +} + +func NewContextMenu(name string) *ContextMenu { + result := &ContextMenu{ + Menu: NewMenu(), + name: name, + } + result.Update() + return result +} + +func (m *ContextMenu) Update() { + m.Menu.Update() + globalApplication.ContextMenu.Add(m.name, m) +} + +func (m *ContextMenu) Destroy() { + globalApplication.ContextMenu.Remove(m.name) +} + +type Menu struct { + items []*MenuItem + label string + + impl menuImpl +} + +func NewMenu() *Menu { + return &Menu{} +} + +func (m *Menu) Add(label string) *MenuItem { + result := NewMenuItem(label) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddSeparator() { + result := NewMenuItemSeparator() + m.items = append(m.items, result) +} + +func (m *Menu) AddCheckbox(label string, enabled bool) *MenuItem { + result := NewMenuItemCheckbox(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) AddRadio(label string, enabled bool) *MenuItem { + result := NewMenuItemRadio(label, enabled) + m.items = append(m.items, result) + return result +} + +func (m *Menu) Update() { + m.processRadioGroups() + if m.impl == nil { + m.impl = newMenuImpl(m) + } + m.impl.update() +} + +// Clear all menu items +func (m *Menu) Clear() { + for _, item := range m.items { + removeMenuItemByID(item.id) + } + m.items = nil +} + +func (m *Menu) Destroy() { + for _, item := range m.items { + item.Destroy() + } + m.items = nil +} + +func (m *Menu) AddSubmenu(s string) *Menu { + result := NewSubMenuItem(s) + m.items = append(m.items, result) + return result.submenu +} + +func (m *Menu) AddRole(role Role) *Menu { + result := NewRole(role) + if result != nil { + m.items = append(m.items, result) + } + return m +} + +func (m *Menu) processRadioGroups() { + var radioGroup []*MenuItem + + closeOutRadioGroups := func() { + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + radioGroup = []*MenuItem{} + } + } + + for _, item := range m.items { + if item.itemType != radio { + closeOutRadioGroups() + } + if item.itemType == submenu { + item.submenu.processRadioGroups() + continue + } + if item.itemType == radio { + radioGroup = append(radioGroup, item) + } + } + closeOutRadioGroups() +} + +func (m *Menu) SetLabel(label string) { + m.label = label +} + +func (m *Menu) setContextData(data *ContextMenuData) { + for _, item := range m.items { + item.setContextData(data) + } +} + +// FindByLabel recursively searches for a menu item with the given label +// and returns the first match, or nil if not found. +func (m *Menu) FindByLabel(label string) *MenuItem { + for _, item := range m.items { + if item.label == label { + return item + } + if item.submenu != nil { + found := item.submenu.FindByLabel(label) + if found != nil { + return found + } + } + } + return nil +} + +// FindByRole recursively searches for a menu item with the given role +// and returns the first match, or nil if not found. +func (m *Menu) FindByRole(role Role) *MenuItem { + for _, item := range m.items { + if item.role == role { + return item + } + if item.submenu != nil { + found := item.submenu.FindByRole(role) + if found != nil { + return found + } + } + } + return nil +} + +func (m *Menu) RemoveMenuItem(target *MenuItem) { + for i, item := range m.items { + if item == target { + // Remove the item from the slice + m.items = append(m.items[:i], m.items[i+1:]...) + break + } + if item.submenu != nil { + item.submenu.RemoveMenuItem(target) + } + } +} + +// ItemAt returns the menu item at the given index, or nil if the index is out of bounds. +func (m *Menu) ItemAt(index int) *MenuItem { + if index < 0 || index >= len(m.items) { + return nil + } + return m.items[index] +} + +// Clone recursively clones the menu and all its submenus. +func (m *Menu) Clone() *Menu { + result := &Menu{ + label: m.label, + } + for _, item := range m.items { + result.items = append(result.items, item.Clone()) + } + return result +} + +// Append menu to an existing menu +func (m *Menu) Append(in *Menu) { + if in == nil { + return + } + m.items = append(m.items, in.items...) +} + +// Prepend menu before an existing menu +func (m *Menu) Prepend(in *Menu) { + m.items = append(in.items, m.items...) +} + +func (a *App) NewMenu() *Menu { + return a.Menu.New() +} + +func NewMenuFromItems(item *MenuItem, items ...*MenuItem) *Menu { + result := &Menu{ + items: []*MenuItem{item}, + } + result.items = append(result.items, items...) + return result +} + +func NewSubmenu(s string, items *Menu) *MenuItem { + result := NewSubMenuItem(s) + result.submenu = items + return result +} diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go new file mode 100644 index 000000000..a17031489 --- /dev/null +++ b/v3/pkg/application/menu_darwin.go @@ -0,0 +1,126 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.10 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "menuitem_darwin.h" + +extern void setMenuItemChecked(void*, unsigned int, bool); +extern void setMenuItemBitmap(void*, unsigned char*, int); + +// Clear and release all menu items in the menu +void clearMenu(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu removeAllItems]; +} + + +// Create a new NSMenu +void* createNSMenu(char* label) { + NSMenu *menu = [[NSMenu alloc] init]; + if( label != NULL && strlen(label) > 0 ) { + menu.title = [NSString stringWithUTF8String:label]; + free(label); + } + [menu setAutoenablesItems:NO]; + return (void*)menu; +} + +void addMenuItem(void* nsMenu, void* nsMenuItem) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:nsMenuItem]; +} + +// add seperator to menu +void addMenuSeparator(void* nsMenu) { + NSMenu *menu = (NSMenu *)nsMenu; + [menu addItem:[NSMenuItem separatorItem]]; +} + +// Set the submenu of a menu item +void setMenuItemSubmenu(void* nsMenuItem, void* nsMenu) { + NSMenuItem *menuItem = (NSMenuItem *)nsMenuItem; + NSMenu *menu = (NSMenu *)nsMenu; + [menuItem setSubmenu:menu]; +} + +// Add services menu +static void addServicesMenu(void* menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setServicesMenu:nsMenu]; +} + + +*/ +import "C" +import "unsafe" + +type macosMenu struct { + menu *Menu + + nsMenu unsafe.Pointer +} + +func newMenuImpl(menu *Menu) *macosMenu { + result := &macosMenu{ + menu: menu, + } + return result +} + +func (m *macosMenu) update() { + InvokeSync(func() { + if m.nsMenu == nil { + m.nsMenu = C.createNSMenu(C.CString(m.menu.label)) + } else { + C.clearMenu(m.nsMenu) + } + m.processMenu(m.nsMenu, m.menu) + }) +} + +func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { + for _, item := range menu.items { + switch item.itemType { + case submenu: + submenu := item.submenu + nsSubmenu := C.createNSMenu(C.CString(item.label)) + m.processMenu(nsSubmenu, submenu) + menuItem := newMenuItemImpl(item) + item.impl = menuItem + C.addMenuItem(parent, menuItem.nsMenuItem) + C.setMenuItemSubmenu(menuItem.nsMenuItem, nsSubmenu) + if item.role == ServicesMenu { + C.addServicesMenu(nsSubmenu) + } + case text, checkbox, radio: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + if item.hidden { + menuItem.setHidden(true) + } + C.addMenuItem(parent, menuItem.nsMenuItem) + case separator: + C.addMenuSeparator(parent) + } + if item.bitmap != nil { + macMenuItem := item.impl.(*macosMenuItem) + C.setMenuItemBitmap(macMenuItem.nsMenuItem, (*C.uchar)(&item.bitmap[0]), C.int(len(item.bitmap))) + } + + } +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_linux.go b/v3/pkg/application/menu_linux.go new file mode 100644 index 000000000..b6235c2da --- /dev/null +++ b/v3/pkg/application/menu_linux.go @@ -0,0 +1,118 @@ +//go:build linux + +package application + +type linuxMenu struct { + menu *Menu + native pointer +} + +func newMenuImpl(menu *Menu) *linuxMenu { + result := &linuxMenu{ + menu: menu, + native: menuBarNew(), + } + return result +} + +func (m *linuxMenu) run() { + m.update() +} + +func (m *linuxMenu) update() { + m.processMenu(m.menu) +} + +func (m *linuxMenu) processMenu(menu *Menu) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } + var currentRadioGroup GSListPointer + + for _, item := range menu.items { + // drop the group if we have run out of radio items + if item.itemType != radio { + currentRadioGroup = nilRadioGroup + } + + switch item.itemType { + case submenu: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.processMenu(item.submenu) + m.addSubMenuToItem(item.submenu, item) + m.addMenuItem(menu, item) + case text, checkbox: + menuItem := newMenuItemImpl(item) + item.impl = menuItem + m.addMenuItem(menu, item) + case radio: + menuItem := newRadioItemImpl(item, currentRadioGroup) + item.impl = menuItem + m.addMenuItem(menu, item) + currentRadioGroup = menuGetRadioGroup(menuItem) + case separator: + m.addMenuSeparator(menu) + } + + } + + for _, item := range menu.items { + if item.callback != nil { + m.attachHandler(item) + } + } + +} + +func (m *linuxMenu) attachHandler(item *MenuItem) { + (item.impl).(*linuxMenuItem).handlerId = attachMenuHandler(item) +} + +func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) { + if menu.impl == nil { + menu.impl = &linuxMenu{ + menu: menu, + native: menuNew(), + } + } + menuSetSubmenu(item, menu) +} + +func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) { + menuAppend(parent, menu) +} + +func (m *linuxMenu) addMenuSeparator(menu *Menu) { + menuAddSeparator(menu) + +} + +func (m *linuxMenu) addServicesMenu(menu *Menu) { + // FIXME: Should this be required? +} + +func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { + impl := newMenuImpl(&Menu{label: name}) + menu := &Menu{ + label: name, + items: items, + impl: impl, + } + impl.menu = menu + return menu +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(AppMenu) + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menu_manager.go b/v3/pkg/application/menu_manager.go new file mode 100644 index 000000000..4c3a36c83 --- /dev/null +++ b/v3/pkg/application/menu_manager.go @@ -0,0 +1,55 @@ +package application + +// MenuManager manages menu-related operations +type MenuManager struct { + app *App +} + +// newMenuManager creates a new MenuManager instance +func newMenuManager(app *App) *MenuManager { + return &MenuManager{ + app: app, + } +} + +// Set sets the application menu +func (mm *MenuManager) Set(menu *Menu) { + mm.SetApplicationMenu(menu) +} + +// SetApplicationMenu sets the application menu +func (mm *MenuManager) SetApplicationMenu(menu *Menu) { + mm.app.applicationMenu = menu + if mm.app.impl != nil { + mm.app.impl.setApplicationMenu(menu) + } +} + +// GetApplicationMenu returns the current application menu +func (mm *MenuManager) GetApplicationMenu() *Menu { + return mm.app.applicationMenu +} + +// New creates a new menu +func (mm *MenuManager) New() *Menu { + return &Menu{} +} + +// ShowAbout shows the about dialog +func (mm *MenuManager) ShowAbout() { + if mm.app.impl != nil { + mm.app.impl.showAboutDialog(mm.app.options.Name, mm.app.options.Description, mm.app.options.Icon) + } +} + +// handleMenuItemClicked handles menu item click events (internal use) +func (mm *MenuManager) handleMenuItemClicked(menuItemID uint) { + defer handlePanic() + + menuItem := getMenuItemByID(menuItemID) + if menuItem == nil { + mm.app.warning("MenuItem #%d not found", menuItemID) + return + } + menuItem.handleClick() +} diff --git a/v3/pkg/application/menu_test.go b/v3/pkg/application/menu_test.go new file mode 100644 index 000000000..88aeb5724 --- /dev/null +++ b/v3/pkg/application/menu_test.go @@ -0,0 +1,140 @@ +package application_test + +import ( + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func TestMenu_FindByLabel(t *testing.T) { + tests := []struct { + name string + menu *application.Menu + label string + shouldError bool + }{ + { + name: "Find top-level item", + menu: application.NewMenuFromItems( + application.NewMenuItem("Target"), + ), + label: "Target", + shouldError: false, + }, + { + name: "Find item in submenu", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewSubmenu("Submenu", application.NewMenuFromItems( + application.NewMenuItem("Subitem 1"), + application.NewMenuItem("Target"), + )), + ), + label: "Target", + shouldError: false, + }, + { + name: "Not find item", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewSubmenu("Submenu", application.NewMenuFromItems( + application.NewMenuItem("Subitem 1"), + application.NewMenuItem("Target"), + )), + ), + label: "Random", + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + found := test.menu.FindByLabel(test.label) + if test.shouldError && found != nil { + t.Errorf("Expected error, but found %v", found) + } + if !test.shouldError && found == nil { + t.Errorf("Expected item, but found none") + } + }) + } +} + +func TestMenu_ItemAt(t *testing.T) { + tests := []struct { + name string + menu *application.Menu + index int + shouldError bool + }{ + { + name: "Valid index", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + application.NewMenuItem("Target"), + ), + index: 2, + shouldError: false, + }, + { + name: "Index out of bounds (negative)", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + ), + index: -1, + shouldError: true, + }, + { + name: "Index out of bounds (too large)", + menu: application.NewMenuFromItems( + application.NewMenuItem("Item 1"), + application.NewMenuItem("Item 2"), + ), + index: 2, + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + item := test.menu.ItemAt(test.index) + if test.shouldError && item != nil { + t.Errorf("Expected error, but found %v", item) + } + if !test.shouldError && item == nil { + t.Errorf("Expected item, but found none") + } + }) + } +} + +func TestMenu_RemoveMenuItem(t *testing.T) { + itemToRemove := application.NewMenuItem("Target") + itemToKeep := application.NewMenuItem("Item 1") + + tests := []struct { + name string + menu *application.Menu + item *application.MenuItem + shouldFind bool + }{ + { + name: "Remove existing item", + menu: application.NewMenuFromItems(itemToKeep, itemToRemove), + item: itemToRemove, + shouldFind: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.menu.RemoveMenuItem(test.item) + found := test.menu.FindByLabel(test.item.Label()) + if !test.shouldFind && found != nil { + t.Errorf("Expected item to be removed, but found %v", found) + } + }) + } +} diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go new file mode 100644 index 000000000..6954cdec1 --- /dev/null +++ b/v3/pkg/application/menu_windows.go @@ -0,0 +1,140 @@ +//go:build windows + +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" +) + +type windowsMenu struct { + menu *Menu + parentWindow *windowsWebviewWindow + + hWnd w32.HWND + hMenu w32.HMENU + currentMenuID int + menuMapping map[int]*MenuItem + checkboxItems []*Menu +} + +func newMenuImpl(menu *Menu) *windowsMenu { + result := &windowsMenu{ + menu: menu, + menuMapping: make(map[int]*MenuItem), + } + + return result +} + +func (w *windowsMenu) update() { + if w.hMenu != 0 { + w32.DestroyMenu(w.hMenu) + } + w.hMenu = w32.NewPopupMenu() + w.processMenu(w.hMenu, w.menu) +} + +func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { + for _, item := range inputMenu.items { + w.currentMenuID++ + itemID := w.currentMenuID + w.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + + if item.Hidden() { + if item.accelerator != nil && item.callback != nil { + if w.parentWindow != nil { + w.parentWindow.parent.removeMenuBinding(item.accelerator) + } else { + globalApplication.KeyBinding.Remove(item.accelerator.String()) + } + } + } + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + if item.itemType == radio { + flags = flags | w32.MFT_RADIOCHECK + } + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := w32.CreateMenu() + w.processMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + } + + thisText := item.Label() + if item.accelerator != nil && item.callback != nil { + if w.parentWindow != nil { + w.parentWindow.parent.addMenuBinding(item.accelerator, item) + } else { + globalApplication.KeyBinding.Add(item.accelerator.String(), func(w *WebviewWindow) { + item.handleClick() + }) + } + thisText = thisText + "\t" + item.accelerator.String() + } + var menuText = w32.MustStringToUTF16Ptr(thisText) + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + + w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText) + if item.bitmap != nil { + w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) + } + } +} + +func (w *windowsMenu) ShowAtCursor() { + InvokeSync(func() { + x, y, ok := w32.GetCursorPos() + if !ok { + return + } + w.ShowAt(x, y) + }) +} + +func (w *windowsMenu) ShowAt(x int, y int) { + w.update() + w32.TrackPopupMenuEx(w.hMenu, + w32.TPM_LEFTALIGN, + int32(x), + int32(y), + w.hWnd, + nil) + w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0) +} + +func (w *windowsMenu) ProcessCommand(cmdMsgID int) { + item := w.menuMapping[cmdMsgID] + if item == nil { + return + } + item.handleClick() +} + +func DefaultApplicationMenu() *Menu { + menu := NewMenu() + menu.AddRole(FileMenu) + menu.AddRole(EditMenu) + menu.AddRole(ViewMenu) + menu.AddRole(WindowMenu) + menu.AddRole(HelpMenu) + return menu +} diff --git a/v3/pkg/application/menuitem.go b/v3/pkg/application/menuitem.go new file mode 100644 index 000000000..5d11a87c7 --- /dev/null +++ b/v3/pkg/application/menuitem.go @@ -0,0 +1,456 @@ +package application + +import ( + "sync" + "sync/atomic" +) + +type menuItemType int + +const ( + text menuItemType = iota + separator + checkbox + radio + submenu +) + +var menuItemID uintptr +var menuItemMap = make(map[uint]*MenuItem) +var menuItemMapLock sync.Mutex + +func addToMenuItemMap(menuItem *MenuItem) { + menuItemMapLock.Lock() + menuItemMap[menuItem.id] = menuItem + menuItemMapLock.Unlock() +} + +func getMenuItemByID(id uint) *MenuItem { + menuItemMapLock.Lock() + defer menuItemMapLock.Unlock() + return menuItemMap[id] +} + +func removeMenuItemByID(id uint) { + menuItemMapLock.Lock() + defer menuItemMapLock.Unlock() + delete(menuItemMap, id) +} + +type menuItemImpl interface { + setTooltip(s string) + setLabel(s string) + setDisabled(disabled bool) + setChecked(checked bool) + setAccelerator(accelerator *accelerator) + setHidden(hidden bool) + setBitmap(bitmap []byte) + destroy() +} + +type MenuItem struct { + id uint + label string + tooltip string + disabled bool + checked bool + hidden bool + bitmap []byte + submenu *Menu + callback func(*Context) + itemType menuItemType + accelerator *accelerator + role Role + contextMenuData *ContextMenuData + + impl menuItemImpl + radioGroupMembers []*MenuItem +} + +func NewMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: text, + } + addToMenuItemMap(result) + return result +} + +func NewMenuItemSeparator() *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + itemType: separator, + } + return result +} + +func NewMenuItemCheckbox(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: checkbox, + } + addToMenuItemMap(result) + return result +} + +func NewMenuItemRadio(label string, checked bool) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + checked: checked, + itemType: radio, + } + addToMenuItemMap(result) + return result +} + +func NewSubMenuItem(label string) *MenuItem { + result := &MenuItem{ + id: uint(atomic.AddUintptr(&menuItemID, 1)), + label: label, + itemType: submenu, + submenu: &Menu{ + label: label, + }, + } + addToMenuItemMap(result) + return result +} + +func NewRole(role Role) *MenuItem { + var result *MenuItem + switch role { + case AppMenu: + result = NewAppMenu() + case EditMenu: + result = NewEditMenu() + case FileMenu: + result = NewFileMenu() + case ViewMenu: + result = NewViewMenu() + case ServicesMenu: + return NewServicesMenu() + case SpeechMenu: + result = NewSpeechMenu() + case WindowMenu: + result = NewWindowMenu() + case HelpMenu: + result = NewHelpMenu() + case Hide: + result = NewHideMenuItem() + case Front: + result = NewFrontMenuItem() + case HideOthers: + result = NewHideOthersMenuItem() + case UnHide: + result = NewUnhideMenuItem() + case Undo: + result = NewUndoMenuItem() + case Redo: + result = NewRedoMenuItem() + case Cut: + result = NewCutMenuItem() + case Copy: + result = NewCopyMenuItem() + case Paste: + result = NewPasteMenuItem() + case PasteAndMatchStyle: + result = NewPasteAndMatchStyleMenuItem() + case SelectAll: + result = NewSelectAllMenuItem() + case Delete: + result = NewDeleteMenuItem() + case Quit: + result = NewQuitMenuItem() + case CloseWindow: + result = NewCloseMenuItem() + case About: + result = NewAboutMenuItem() + case Reload: + result = NewReloadMenuItem() + case ForceReload: + result = NewForceReloadMenuItem() + case ToggleFullscreen: + result = NewToggleFullscreenMenuItem() + case OpenDevTools: + result = NewOpenDevToolsMenuItem() + case ResetZoom: + result = NewZoomResetMenuItem() + case ZoomIn: + result = NewZoomInMenuItem() + case ZoomOut: + result = NewZoomOutMenuItem() + case Minimise: + result = NewMinimiseMenuItem() + case Zoom: + result = NewZoomMenuItem() + case FullScreen: + result = NewFullScreenMenuItem() + case Print: + result = NewPrintMenuItem() + case PageLayout: + result = NewPageLayoutMenuItem() + case NoRole: + case ShowAll: + result = NewShowAllMenuItem() + case BringAllToFront: + result = NewBringAllToFrontMenuItem() + case NewFile: + result = NewNewFileMenuItem() + case Open: + result = NewOpenMenuItem() + case Save: + result = NewSaveMenuItem() + case SaveAs: + result = NewSaveAsMenuItem() + case StartSpeaking: + result = NewStartSpeakingMenuItem() + case StopSpeaking: + result = NewStopSpeakingMenuItem() + case Revert: + result = NewRevertMenuItem() + case Find: + result = NewFindMenuItem() + case FindAndReplace: + result = NewFindAndReplaceMenuItem() + case FindNext: + result = NewFindNextMenuItem() + case FindPrevious: + result = NewFindPreviousMenuItem() + case Help: + result = NewHelpMenuItem() + + default: + globalApplication.error("no support for role: %v", role) + } + + if result != nil { + result.role = role + } + + return result +} + +func NewServicesMenu() *MenuItem { + serviceMenu := NewSubMenuItem("Services") + serviceMenu.role = ServicesMenu + return serviceMenu +} + +func (m *MenuItem) handleClick() { + var ctx = newContext(). + withClickedMenuItem(m). + withContextMenuData(m.contextMenuData) + if m.itemType == checkbox { + m.checked = !m.checked + ctx.withChecked(m.checked) + if m.impl != nil { + m.impl.setChecked(m.checked) + } + } + if m.itemType == radio { + for _, member := range m.radioGroupMembers { + member.checked = false + if member.impl != nil { + member.impl.setChecked(false) + } + } + m.checked = true + ctx.withChecked(true) + if m.impl != nil { + m.impl.setChecked(true) + } + } + if m.callback != nil { + go func() { + defer handlePanic() + m.callback(ctx) + }() + } +} + +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { + accelerator, err := parseAccelerator(shortcut) + if err != nil { + globalApplication.error("invalid accelerator: %w", err) + return m + } + m.accelerator = accelerator + if m.impl != nil { + m.impl.setAccelerator(accelerator) + } + return m +} + +func (m *MenuItem) GetAccelerator() string { + if m.accelerator == nil { + return "" + } + return m.accelerator.String() +} + +func (m *MenuItem) RemoveAccelerator() { + m.accelerator = nil +} + +func (m *MenuItem) SetTooltip(s string) *MenuItem { + m.tooltip = s + if m.impl != nil { + m.impl.setTooltip(s) + } + return m +} + +func (m *MenuItem) SetRole(role Role) *MenuItem { + m.role = role + return m +} + +func (m *MenuItem) SetLabel(s string) *MenuItem { + m.label = s + if m.impl != nil { + m.impl.setLabel(s) + } + return m +} + +func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { + m.disabled = !enabled + if m.impl != nil { + m.impl.setDisabled(m.disabled) + } + return m +} + +func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem { + m.bitmap = bitmap + if m.impl != nil { + m.impl.setBitmap(bitmap) + } + return m +} + +func (m *MenuItem) SetChecked(checked bool) *MenuItem { + m.checked = checked + if m.impl != nil { + m.impl.setChecked(m.checked) + } + return m +} + +func (m *MenuItem) SetHidden(hidden bool) *MenuItem { + m.hidden = hidden + if m.impl != nil { + m.impl.setHidden(m.hidden) + } + return m +} + +// GetSubmenu returns the submenu of the MenuItem. +// If the MenuItem is not a submenu, it returns nil. +func (m *MenuItem) GetSubmenu() *Menu { + return m.submenu +} + +func (m *MenuItem) Checked() bool { + return m.checked +} + +func (m *MenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *MenuItem) IsSubmenu() bool { + return m.itemType == submenu +} + +func (m *MenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *MenuItem) IsRadio() bool { + return m.itemType == radio +} + +func (m *MenuItem) Hidden() bool { + return m.hidden +} + +func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { + m.callback = f + return m +} + +func (m *MenuItem) Label() string { + return m.label +} + +func (m *MenuItem) Tooltip() string { + return m.tooltip +} + +func (m *MenuItem) Enabled() bool { + return !m.disabled +} + +func (m *MenuItem) setContextData(data *ContextMenuData) { + m.contextMenuData = data + if m.submenu != nil { + m.submenu.setContextData(data) + } +} + +// Clone returns a deep copy of the MenuItem +func (m *MenuItem) Clone() *MenuItem { + result := &MenuItem{ + id: m.id, + label: m.label, + tooltip: m.tooltip, + disabled: m.disabled, + checked: m.checked, + hidden: m.hidden, + bitmap: m.bitmap, + callback: m.callback, + itemType: m.itemType, + role: m.role, + } + if m.submenu != nil { + result.submenu = m.submenu.Clone() + } + if m.accelerator != nil { + result.accelerator = m.accelerator.clone() + } + if m.contextMenuData != nil { + result.contextMenuData = m.contextMenuData.clone() + } + return result +} + +func (m *MenuItem) Destroy() { + + removeMenuItemByID(m.id) + + // Clean up resources + if m.impl != nil { + m.impl.destroy() + } + if m.submenu != nil { + m.submenu.Destroy() + m.submenu = nil + } + + if m.contextMenuData != nil { + m.contextMenuData = nil + } + + if m.accelerator != nil { + m.accelerator = nil + } + + m.callback = nil + m.radioGroupMembers = nil + +} diff --git a/v3/pkg/application/menuitem_darwin.go b/v3/pkg/application/menuitem_darwin.go new file mode 100644 index 000000000..e3d3401da --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.go @@ -0,0 +1,382 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "application_darwin.h" + +#define unicode(input) [NSString stringWithFormat:@"%C", input] + +// Create menu item +void* newMenuItem(unsigned int menuItemID, char *label, bool disabled, char* tooltip, char* selector) { + MenuItem *menuItem = [MenuItem new]; + + // Label + menuItem.title = [NSString stringWithUTF8String:label]; + + if( disabled ) { + [menuItem setTarget:nil]; + } else { + if (selector != NULL) { + menuItem.action = NSSelectorFromString([NSString stringWithUTF8String:selector]); + menuItem.target = nil; // Allow the action to be sent up the responder chain + } else { + menuItem.action = @selector(handleClick); + menuItem.target = menuItem; + } + } + menuItem.menuItemID = menuItemID; + + menuItem.enabled = !disabled; + + // Tooltip + if( tooltip != NULL ) { + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; + free(tooltip); + } + + // Set the tag + [menuItem setTag:menuItemID]; + + return (void*)menuItem; +} + +// set menu item label +void setMenuItemLabel(void* nsMenuItem, char *label) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.title = [NSString stringWithUTF8String:label]; + free(label); + }); +} + +// set menu item disabled +void setMenuItemDisabled(void* nsMenuItem, bool disabled) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setEnabled:!disabled]; + // remove target + if( disabled ) { + [menuItem setTarget:nil]; + } else { + [menuItem setTarget:menuItem]; + } + }); +} + +// set menu item hidden +void setMenuItemHidden(void* nsMenuItem, bool hidden) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem setHidden:hidden]; + }); +} + +// set menu item tooltip +void setMenuItemTooltip(void* nsMenuItem, char *tooltip) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; + free(tooltip); + }); +} + +// Check menu item +void setMenuItemChecked(void* nsMenuItem, bool checked) { + dispatch_async(dispatch_get_main_queue(), ^{ + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.state = checked ? NSControlStateValueOn : NSControlStateValueOff; + }); +} + +NSString* translateKey(NSString* key) { + + // Guard against no accelerator key + if( key == NULL ) { + return @""; + } + + if( [key isEqualToString:@"backspace"] ) { + return unicode(0x0008); + } + if( [key isEqualToString:@"tab"] ) { + return unicode(0x0009); + } + if( [key isEqualToString:@"return"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"enter"] ) { + return unicode(0x000d); + } + if( [key isEqualToString:@"escape"] ) { + return unicode(0x001b); + } + if( [key isEqualToString:@"left"] ) { + return unicode(0xf702); + } + if( [key isEqualToString:@"right"] ) { + return unicode(0xf703); + } + if( [key isEqualToString:@"up"] ) { + return unicode(0xf700); + } + if( [key isEqualToString:@"down"] ) { + return unicode(0xf701); + } + if( [key isEqualToString:@"space"] ) { + return unicode(0x0020); + } + if( [key isEqualToString:@"delete"] ) { + return unicode(0x007f); + } + if( [key isEqualToString:@"home"] ) { + return unicode(0x2196); + } + if( [key isEqualToString:@"end"] ) { + return unicode(0x2198); + } + if( [key isEqualToString:@"page up"] ) { + return unicode(0x21de); + } + if( [key isEqualToString:@"page down"] ) { + return unicode(0x21df); + } + if( [key isEqualToString:@"f1"] ) { + return unicode(0xf704); + } + if( [key isEqualToString:@"f2"] ) { + return unicode(0xf705); + } + if( [key isEqualToString:@"f3"] ) { + return unicode(0xf706); + } + if( [key isEqualToString:@"f4"] ) { + return unicode(0xf707); + } + if( [key isEqualToString:@"f5"] ) { + return unicode(0xf708); + } + if( [key isEqualToString:@"f6"] ) { + return unicode(0xf709); + } + if( [key isEqualToString:@"f7"] ) { + return unicode(0xf70a); + } + if( [key isEqualToString:@"f8"] ) { + return unicode(0xf70b); + } + if( [key isEqualToString:@"f9"] ) { + return unicode(0xf70c); + } + if( [key isEqualToString:@"f10"] ) { + return unicode(0xf70d); + } + if( [key isEqualToString:@"f11"] ) { + return unicode(0xf70e); + } + if( [key isEqualToString:@"f12"] ) { + return unicode(0xf70f); + } + if( [key isEqualToString:@"f13"] ) { + return unicode(0xf710); + } + if( [key isEqualToString:@"f14"] ) { + return unicode(0xf711); + } + if( [key isEqualToString:@"f15"] ) { + return unicode(0xf712); + } + if( [key isEqualToString:@"f16"] ) { + return unicode(0xf713); + } + if( [key isEqualToString:@"f17"] ) { + return unicode(0xf714); + } + if( [key isEqualToString:@"f18"] ) { + return unicode(0xf715); + } + if( [key isEqualToString:@"f19"] ) { + return unicode(0xf716); + } + if( [key isEqualToString:@"f20"] ) { + return unicode(0xf717); + } + if( [key isEqualToString:@"f21"] ) { + return unicode(0xf718); + } + if( [key isEqualToString:@"f22"] ) { + return unicode(0xf719); + } + if( [key isEqualToString:@"f23"] ) { + return unicode(0xf71a); + } + if( [key isEqualToString:@"f24"] ) { + return unicode(0xf71b); + } + if( [key isEqualToString:@"f25"] ) { + return unicode(0xf71c); + } + if( [key isEqualToString:@"f26"] ) { + return unicode(0xf71d); + } + if( [key isEqualToString:@"f27"] ) { + return unicode(0xf71e); + } + if( [key isEqualToString:@"f28"] ) { + return unicode(0xf71f); + } + if( [key isEqualToString:@"f29"] ) { + return unicode(0xf720); + } + if( [key isEqualToString:@"f30"] ) { + return unicode(0xf721); + } + if( [key isEqualToString:@"f31"] ) { + return unicode(0xf722); + } + if( [key isEqualToString:@"f32"] ) { + return unicode(0xf723); + } + if( [key isEqualToString:@"f33"] ) { + return unicode(0xf724); + } + if( [key isEqualToString:@"f34"] ) { + return unicode(0xf725); + } + if( [key isEqualToString:@"f35"] ) { + return unicode(0xf726); + } + if( [key isEqualToString:@"numLock"] ) { + return unicode(0xf739); + } + return key; +} + +// Set the menuitem key equivalent +void setMenuItemKeyEquivalent(void* nsMenuItem, char *key, int modifier) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSString *nskey = [NSString stringWithUTF8String:key]; + menuItem.keyEquivalent = translateKey(nskey); + menuItem.keyEquivalentModifierMask = modifier; + free(key); +} + +// Call the copy selector on the pasteboard +static void copyToPasteboard(char *text) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard setString:[NSString stringWithUTF8String:text] forType:NSPasteboardTypeString]; +} + +// Call the paste selector on the pasteboard +static char *pasteFromPasteboard(void) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *text = [pasteboard stringForType:NSPasteboardTypeString]; + if( text == nil ) { + return NULL; + } + return strdup([text UTF8String]); +} + +void performSelectorOnMainThreadForFirstResponder(SEL selector) { + NSWindow *activeWindow = [[NSApplication sharedApplication] keyWindow]; + if (activeWindow) { + [activeWindow performSelectorOnMainThread:selector withObject:nil waitUntilDone:YES]; + } +} + +void setMenuItemBitmap(void* nsMenuItem, unsigned char *bitmap, int length) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:bitmap length:length]]; + [menuItem setImage:image]; +} + +void destroyMenuItem(void* nsMenuItem) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + [menuItem release]; +} +*/ +import "C" +import ( + "unsafe" +) + +type macosMenuItem struct { + menuItem *MenuItem + + nsMenuItem unsafe.Pointer +} + +func (m macosMenuItem) setTooltip(tooltip string) { + C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip)) +} + +func (m macosMenuItem) setLabel(s string) { + C.setMenuItemLabel(m.nsMenuItem, C.CString(s)) +} + +func (m macosMenuItem) setDisabled(disabled bool) { + C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled)) +} + +func (m macosMenuItem) setChecked(checked bool) { + C.setMenuItemChecked(m.nsMenuItem, C.bool(checked)) +} + +func (m macosMenuItem) setHidden(hidden bool) { + C.setMenuItemHidden(m.nsMenuItem, C.bool(hidden)) +} + +func (m macosMenuItem) setBitmap(bitmap []byte) { + C.setMenuItemBitmap(m.nsMenuItem, (*C.uchar)(&bitmap[0]), C.int(len(bitmap))) +} + +func (m macosMenuItem) setAccelerator(accelerator *accelerator) { + // Set the keyboard shortcut of the menu item + var modifier C.int + var key *C.char + if accelerator != nil { + modifier = C.int(toMacModifier(accelerator.Modifiers)) + key = C.CString(accelerator.Key) + } + + // Convert the key to a string + C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func (m macosMenuItem) destroy() { + C.destroyMenuItem(m.nsMenuItem) +} + +func newMenuItemImpl(item *MenuItem) *macosMenuItem { + result := &macosMenuItem{ + menuItem: item, + } + + selector := getSelectorForRole(item.role) + if selector != nil { + defer C.free(unsafe.Pointer(selector)) + } + result.nsMenuItem = unsafe.Pointer(C.newMenuItem( + C.uint(item.id), + C.CString(item.label), + C.bool(item.disabled), + C.CString(item.tooltip), + selector, + )) + + switch item.itemType { + case checkbox, radio: + C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked)) + } + + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + return result +} diff --git a/v3/pkg/application/menuitem_darwin.h b/v3/pkg/application/menuitem_darwin.h new file mode 100644 index 000000000..8260d4dfd --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.h @@ -0,0 +1,17 @@ +#ifndef MenuItemDelegate_h +#define MenuItemDelegate_h + +#import + +extern void processMenuItemClick(unsigned int); + +@interface MenuItem : NSMenuItem + +@property unsigned int menuItemID; + +- (void) handleClick; + +@end + + +#endif /* MenuItemDelegate_h */ diff --git a/v3/pkg/application/menuitem_darwin.m b/v3/pkg/application/menuitem_darwin.m new file mode 100644 index 000000000..39369502d --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.m @@ -0,0 +1,13 @@ +//go:build darwin + +#import + +#import "menuitem_darwin.h" + +@implementation MenuItem + +- (void) handleClick { + processMenuItemClick(self.menuItemID); +} + +@end diff --git a/v3/pkg/application/menuitem_dev.go b/v3/pkg/application/menuitem_dev.go new file mode 100644 index 000000000..4ce99fd66 --- /dev/null +++ b/v3/pkg/application/menuitem_dev.go @@ -0,0 +1,14 @@ +//go:build !production || devtools + +package application + +func NewOpenDevToolsMenuItem() *MenuItem { + return NewMenuItem("Open Developer Tools"). + SetAccelerator("Alt+Command+I"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.OpenDevTools() + } + }) +} diff --git a/v3/pkg/application/menuitem_linux.go b/v3/pkg/application/menuitem_linux.go new file mode 100644 index 000000000..68a3ddd4a --- /dev/null +++ b/v3/pkg/application/menuitem_linux.go @@ -0,0 +1,436 @@ +//go:build linux + +package application + +import ( + "fmt" + "runtime" +) + +type linuxMenuItem struct { + menuItem *MenuItem + native pointer + handlerId uint +} + +func (l linuxMenuItem) setTooltip(tooltip string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetToolTip(l.native, tooltip) + }) +} + +func (l linuxMenuItem) destroy() { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemDestroy(l.native) + }) +} + +func (l linuxMenuItem) blockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, true) + } +} +func (l linuxMenuItem) setBitmap(data []byte) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetBitmap(l.native, data) + }) +} + +func (l linuxMenuItem) unBlockSignal() { + if l.handlerId != 0 { + menuItemSignalBlock(l.native, l.handlerId, false) + } +} + +func (l linuxMenuItem) setLabel(s string) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetLabel(l.native, s) + }) +} + +func (l linuxMenuItem) isChecked() bool { + return menuItemChecked(l.native) +} + +func (l linuxMenuItem) setDisabled(disabled bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetDisabled(l.native, disabled) + }) +} + +func (l linuxMenuItem) setChecked(checked bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + menuItemSetChecked(l.native, checked) + }) +} + +func (l linuxMenuItem) setHidden(hidden bool) { + InvokeSync(func() { + l.blockSignal() + defer l.unBlockSignal() + widgetSetVisible(l.native, hidden) + }) +} + +func (l linuxMenuItem) setAccelerator(accelerator *accelerator) { + fmt.Println("setAccelerator", accelerator) + // Set the keyboard shortcut of the menu item + // var modifier C.int + // var key *C.char + if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + } + + // Convert the key to a string + // C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func newMenuItemImpl(item *MenuItem) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + } + switch item.itemType { + case text: + result.native = menuItemNew(item.label, item.bitmap) + + case checkbox: + result.native = menuCheckItemNew(item.label, item.bitmap) + result.setChecked(item.checked) + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + case submenu: + result.native = menuItemNew(item.label, item.bitmap) + + default: + panic(fmt.Sprintf("Unknown menu type: %v", item.itemType)) + } + result.setDisabled(result.menuItem.disabled) + return result +} + +func newRadioItemImpl(item *MenuItem, group GSListPointer) *linuxMenuItem { + result := &linuxMenuItem{ + menuItem: item, + native: menuRadioItemNew(group, item.label), + } + result.setChecked(item.checked) + result.setDisabled(result.menuItem.disabled) + return result +} + +func newSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.Add("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). + OnClick(func(ctx *Context) { + // C.startSpeaking() + }) + speechMenu.Add("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). + OnClick(func(ctx *Context) { + // C.stopSpeaking() + }) + subMenu := NewSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func newFrontMenuItem() *MenuItem { + panic("implement me") +} + +func newHideMenuItem() *MenuItem { + return NewMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + OnClick(func(ctx *Context) { + + // C.hideApplication() + }) +} + +func newHideOthersMenuItem() *MenuItem { + return NewMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + OnClick(func(ctx *Context) { + // C.hideOthers() + }) +} + +func newUnhideMenuItem() *MenuItem { + return NewMenuItem("Show All"). + OnClick(func(ctx *Context) { + // C.showAll() + }) +} + +func newUndoMenuItem() *MenuItem { + return NewMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z"). + OnClick(func(ctx *Context) { + // C.undo() + }) +} + +// newRedoMenuItem creates a new menu item for redoing the last action +func newRedoMenuItem() *MenuItem { + return NewMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z"). + OnClick(func(ctx *Context) { + // C.redo() + }) +} + +func newCutMenuItem() *MenuItem { + return NewMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x"). + OnClick(func(ctx *Context) { + // C.cut() + }) +} + +func newCopyMenuItem() *MenuItem { + return NewMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c"). + OnClick(func(ctx *Context) { + // C.copy() + }) +} + +func newPasteMenuItem() *MenuItem { + return NewMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v"). + OnClick(func(ctx *Context) { + // C.paste() + }) +} + +func newPasteAndMatchStyleMenuItem() *MenuItem { + return NewMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). + OnClick(func(ctx *Context) { + // C.pasteAndMatchStyle() + }) +} + +func newDeleteMenuItem() *MenuItem { + return NewMenuItem("Delete"). + SetAccelerator("backspace"). + OnClick(func(ctx *Context) { + // C.delete() + }) +} + +func newQuitMenuItem() *MenuItem { + return NewMenuItem("Quit " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func newSelectAllMenuItem() *MenuItem { + return NewMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a"). + OnClick(func(ctx *Context) { + // C.selectAll() + }) +} + +func newAboutMenuItem() *MenuItem { + return NewMenuItem("About " + globalApplication.options.Name). + OnClick(func(ctx *Context) { + globalApplication.Menu.ShowAbout() + }) +} + +func newCloseMenuItem() *MenuItem { + return NewMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func newReloadMenuItem() *MenuItem { + return NewMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func newForceReloadMenuItem() *MenuItem { + return NewMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func newToggleFullscreenMenuItem() *MenuItem { + result := NewMenuItem("Toggle Full Screen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS == "darwin" { + result.SetAccelerator("Ctrl+Command+F") + } else { + result.SetAccelerator("F11") + } + return result +} + +func newZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return NewMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func newZoomInMenuItem() *MenuItem { + return NewMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func newZoomOutMenuItem() *MenuItem { + return NewMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func newMinimizeMenuItem() *MenuItem { + return NewMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func newZoomMenuItem() *MenuItem { + return NewMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func newFullScreenMenuItem() *MenuItem { + return NewMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} + +func newPrintMenuItem() *MenuItem { + panic("Implement me") +} + +func newPageLayoutMenuItem() *MenuItem { + panic("Implement me") +} + +func newShowAllMenuItem() *MenuItem { + panic("Implement me") +} + +func newBringAllToFrontMenuItem() *MenuItem { + panic("Implement me") +} + +func newNewFileMenuItem() *MenuItem { + panic("Implement me") +} + +func newOpenMenuItem() *MenuItem { + panic("Implement me") +} + +func newSaveMenuItem() *MenuItem { + panic("Implement me") +} + +func newSaveAsMenuItem() *MenuItem { + panic("Implement me") +} + +func newStartSpeakingMenuItem() *MenuItem { + panic("Implement me") +} + +func newStopSpeakingMenuItem() *MenuItem { + panic("Implement me") +} + +func newRevertMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindAndReplaceMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindNextMenuItem() *MenuItem { + panic("Implement me") +} + +func newFindPreviousMenuItem() *MenuItem { + panic("Implement me") +} + +func newHelpMenuItem() *MenuItem { + panic("Implement me") +} diff --git a/v3/pkg/application/menuitem_production.go b/v3/pkg/application/menuitem_production.go new file mode 100644 index 000000000..b5aac387f --- /dev/null +++ b/v3/pkg/application/menuitem_production.go @@ -0,0 +1,7 @@ +//go:build production && !devtools + +package application + +func NewOpenDevToolsMenuItem() *MenuItem { + return nil +} diff --git a/v3/pkg/application/menuitem_roles.go b/v3/pkg/application/menuitem_roles.go new file mode 100644 index 000000000..f36908884 --- /dev/null +++ b/v3/pkg/application/menuitem_roles.go @@ -0,0 +1,358 @@ +package application + +import "runtime" + +func NewSpeechMenu() *MenuItem { + speechMenu := NewMenu() + speechMenu.AddRole(StartSpeaking) + speechMenu.AddRole(StopSpeaking) + subMenu := NewSubMenuItem("Speech") + subMenu.submenu = speechMenu + return subMenu +} + +func NewHideMenuItem() *MenuItem { + return NewMenuItem("Hide " + globalApplication.options.Name). + SetAccelerator("CmdOrCtrl+h"). + SetRole(Hide) +} + +func NewHideOthersMenuItem() *MenuItem { + return NewMenuItem("Hide Others"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). + SetRole(HideOthers) +} + +func NewFrontMenuItem() *MenuItem { + return NewMenuItem("Bring All to Front") +} + +func NewUnhideMenuItem() *MenuItem { + return NewMenuItem("Show All") +} + +func NewUndoMenuItem() *MenuItem { + result := NewMenuItem("Undo"). + SetAccelerator("CmdOrCtrl+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.undo() + } + }) + } + return result +} + +// NewRedoMenuItem creates a new menu item for redoing the last action +func NewRedoMenuItem() *MenuItem { + result := NewMenuItem("Redo"). + SetAccelerator("CmdOrCtrl+Shift+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.redo() + } + }) + } + return result +} + +func NewCutMenuItem() *MenuItem { + result := NewMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.cut() + } + }) + } + return result +} + +func NewCopyMenuItem() *MenuItem { + result := NewMenuItem("Copy"). + SetAccelerator("CmdOrCtrl+c") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.copy() + } + }) + } + return result +} + +func NewPasteMenuItem() *MenuItem { + result := NewMenuItem("Paste"). + SetAccelerator("CmdOrCtrl+v") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.paste() + } + }) + } + return result +} + +func NewPasteAndMatchStyleMenuItem() *MenuItem { + return NewMenuItem("Paste and Match Style"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v") +} + +func NewDeleteMenuItem() *MenuItem { + result := NewMenuItem("Delete"). + SetAccelerator("backspace") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.delete() + } + }) + } + return result +} + +func NewQuitMenuItem() *MenuItem { + label := "Quit" + if runtime.GOOS == "darwin" { + if globalApplication.options.Name != "" { + label += " " + globalApplication.options.Name + } + } + return NewMenuItem(label). + SetAccelerator("CmdOrCtrl+q"). + OnClick(func(ctx *Context) { + globalApplication.Quit() + }) +} + +func NewSelectAllMenuItem() *MenuItem { + result := NewMenuItem("Select All"). + SetAccelerator("CmdOrCtrl+a") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.selectAll() + } + }) + } + return result +} + +func NewAboutMenuItem() *MenuItem { + label := "About" + if globalApplication.options.Name != "" { + label += " " + globalApplication.options.Name + } + return NewMenuItem(label). + OnClick(func(ctx *Context) { + globalApplication.Menu.ShowAbout() + }) +} + +func NewCloseMenuItem() *MenuItem { + return NewMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func NewReloadMenuItem() *MenuItem { + return NewMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func NewForceReloadMenuItem() *MenuItem { + return NewMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func NewToggleFullscreenMenuItem() *MenuItem { + result := NewMenuItem("Toggle Full Screen"). + SetAccelerator("Ctrl+Command+F"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS != "darwin" { + result.SetAccelerator("F11") + } + return result +} + +func NewZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return NewMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func NewZoomInMenuItem() *MenuItem { + return NewMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func NewZoomOutMenuItem() *MenuItem { + return NewMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func NewMinimiseMenuItem() *MenuItem { + return NewMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Minimise() + } + }) +} + +func NewZoomMenuItem() *MenuItem { + return NewMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} + +func NewFullScreenMenuItem() *MenuItem { + return NewMenuItem("Fullscreen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.Window.Current() + if currentWindow != nil { + currentWindow.Fullscreen() + } + }) +} + +func NewPrintMenuItem() *MenuItem { + return NewMenuItem("Print"). + SetAccelerator("CmdOrCtrl+p") +} + +func NewPageLayoutMenuItem() *MenuItem { + return NewMenuItem("Page Setup..."). + SetAccelerator("CmdOrCtrl+Shift+p") +} + +func NewShowAllMenuItem() *MenuItem { + return NewMenuItem("Show All") +} + +func NewBringAllToFrontMenuItem() *MenuItem { + return NewMenuItem("Bring All to Front") +} + +func NewNewFileMenuItem() *MenuItem { + return NewMenuItem("New File"). + SetAccelerator("CmdOrCtrl+n") +} + +func NewOpenMenuItem() *MenuItem { + return NewMenuItem("Open..."). + SetAccelerator("CmdOrCtrl+o"). + SetRole(Open) +} + +func NewSaveMenuItem() *MenuItem { + return NewMenuItem("Save"). + SetAccelerator("CmdOrCtrl+s") +} + +func NewSaveAsMenuItem() *MenuItem { + return NewMenuItem("Save As..."). + SetAccelerator("CmdOrCtrl+Shift+s") +} + +func NewStartSpeakingMenuItem() *MenuItem { + return NewMenuItem("Start Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+.") +} + +func NewStopSpeakingMenuItem() *MenuItem { + return NewMenuItem("Stop Speaking"). + SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,") +} + +func NewRevertMenuItem() *MenuItem { + return NewMenuItem("Revert"). + SetAccelerator("CmdOrCtrl+r") +} + +func NewFindMenuItem() *MenuItem { + return NewMenuItem("Find..."). + SetAccelerator("CmdOrCtrl+f") +} + +func NewFindAndReplaceMenuItem() *MenuItem { + return NewMenuItem("Find and Replace..."). + SetAccelerator("CmdOrCtrl+Shift+f") +} + +func NewFindNextMenuItem() *MenuItem { + return NewMenuItem("Find Next"). + SetAccelerator("CmdOrCtrl+g") +} + +func NewFindPreviousMenuItem() *MenuItem { + return NewMenuItem("Find Previous"). + SetAccelerator("CmdOrCtrl+Shift+g") +} + +func NewHelpMenuItem() *MenuItem { + return NewMenuItem("Help"). + SetAccelerator("CmdOrCtrl+?") +} diff --git a/v3/pkg/application/menuitem_selectors_darwin.go b/v3/pkg/application/menuitem_selectors_darwin.go new file mode 100644 index 000000000..3fd001897 --- /dev/null +++ b/v3/pkg/application/menuitem_selectors_darwin.go @@ -0,0 +1,57 @@ +// File: v3/pkg/application/menuitem_selectors_darwin.go + +//go:build darwin + +package application + +import "C" + +var roleToSelector = map[Role]string{ + //AppMenu: "", // This is a special case, handled separately + About: "orderFrontStandardAboutPanel:", + //ServicesMenu: "", // This is a submenu, no direct selector + Hide: "hide:", + HideOthers: "hideOtherApplications:", + ShowAll: "unhideAllApplications:", + Quit: "terminate:", + //WindowMenu: "", // This is a submenu, no direct selector + Minimise: "performMiniaturize:", + Zoom: "performZoom:", + BringAllToFront: "arrangeInFront:", + CloseWindow: "performClose:", + //EditMenu: "", // This is a submenu, no direct selector + Undo: "undo:", + Redo: "redo:", + Cut: "cut:", + Copy: "copy:", + Paste: "paste:", + Delete: "delete:", + SelectAll: "selectAll:", + //FindMenu: "", // This is a submenu, no direct selector + Find: "performTextFinderAction:", + FindAndReplace: "performTextFinderAction:", + FindNext: "performTextFinderAction:", + FindPrevious: "performTextFinderAction:", + //ViewMenu: "", // This is a submenu, no direct selector + ToggleFullscreen: "toggleFullScreen:", + //FileMenu: "", // This is a submenu, no direct selector + NewFile: "newDocument:", + Open: "openDocument:", + Save: "saveDocument:", + SaveAs: "saveDocumentAs:", + StartSpeaking: "startSpeaking:", + StopSpeaking: "stopSpeaking:", + Revert: "revertDocumentToSaved:", + Print: "printDocument:", + PageLayout: "runPageLayout:", + //HelpMenu: "", // This is a submenu, no direct selector + Help: "showHelp:", + //No: "", // No specific selector for this role +} + +func getSelectorForRole(role Role) *C.char { + if selector, ok := roleToSelector[role]; ok && selector != "" { + return C.CString(selector) + } + return nil +} diff --git a/v3/pkg/application/menuitem_test.go b/v3/pkg/application/menuitem_test.go new file mode 100644 index 000000000..b2178876f --- /dev/null +++ b/v3/pkg/application/menuitem_test.go @@ -0,0 +1,61 @@ +package application_test + +import ( + "testing" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func TestMenuItem_GetAccelerator(t *testing.T) { + tests := []struct { + name string + menuItem *application.MenuItem + expectedAcc string + }{ + { + name: "Get existing accelerator", + menuItem: application.NewMenuItem("Item 1").SetAccelerator("ctrl+a"), + expectedAcc: "Ctrl+A", + }, + { + name: "Get non-existing accelerator", + menuItem: application.NewMenuItem("Item 2"), + expectedAcc: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + acc := test.menuItem.GetAccelerator() + if acc != test.expectedAcc { + t.Errorf("Expected accelerator to be %v, but got %v", test.expectedAcc, acc) + } + }) + } +} + +func TestMenuItem_RemoveAccelerator(t *testing.T) { + tests := []struct { + name string + menuItem *application.MenuItem + }{ + { + name: "Remove existing accelerator", + menuItem: application.NewMenuItem("Item 1").SetAccelerator("Ctrl+A"), + }, + { + name: "Remove non-existing accelerator", + menuItem: application.NewMenuItem("Item 2"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.menuItem.RemoveAccelerator() + acc := test.menuItem.GetAccelerator() + if acc != "" { + t.Errorf("Expected accelerator to be removed, but got %v", acc) + } + }) + } +} diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go new file mode 100644 index 000000000..941bf2388 --- /dev/null +++ b/v3/pkg/application/menuitem_windows.go @@ -0,0 +1,172 @@ +//go:build windows + +package application + +import ( + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" +) + +type windowsMenuItem struct { + parent *Menu + menuItem *MenuItem + + hMenu w32.HMENU + id int + label string + disabled bool + checked bool + itemType menuItemType + hidden bool + submenu w32.HMENU +} + +func (m *windowsMenuItem) setHidden(hidden bool) { + if hidden && !m.hidden { + m.hidden = true + // Remove from parent menu + w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) + } else if !hidden && m.hidden { + m.hidden = false + // Reinsert into parent menu at correct visible position + var pos int + for _, item := range m.parent.items { + if item == m.menuItem { + break + } + if item.hidden == false { + pos++ + } + } + w32.InsertMenuItem(m.hMenu, uint32(pos), true, m.getMenuInfo()) + } +} + +func (m *windowsMenuItem) Checked() bool { + return m.checked +} + +func (m *windowsMenuItem) IsSeparator() bool { + return m.itemType == separator +} + +func (m *windowsMenuItem) IsCheckbox() bool { + return m.itemType == checkbox +} + +func (m *windowsMenuItem) IsRadio() bool { + return m.itemType == radio +} + +func (m *windowsMenuItem) Enabled() bool { + return !m.disabled +} + +func (m *windowsMenuItem) update() { + w32.SetMenuItemInfo(m.hMenu, uint32(m.id), false, m.getMenuInfo()) +} + +func (m *windowsMenuItem) setLabel(label string) { + m.label = label + m.update() +} + +func (m *windowsMenuItem) setDisabled(disabled bool) { + m.disabled = disabled + m.update() +} + +func (m *windowsMenuItem) setChecked(checked bool) { + m.checked = checked + m.update() +} + +func (m *windowsMenuItem) destroy() { + w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) +} + +func (m *windowsMenuItem) setAccelerator(accelerator *accelerator) { + //// Set the keyboard shortcut of the menu item + //var modifier C.int + //var key *C.char + //if accelerator != nil { + // modifier = C.int(toMacModifier(accelerator.Modifiers)) + // key = C.CString(accelerator.Key) + //} + // + //// Convert the key to a string + //C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) +} + +func (m *windowsMenuItem) setBitmap(bitmap []byte) { + if m.menuItem.bitmap == nil { + return + } + + // Set the icon + err := w32.SetMenuIcons(m.hMenu, m.id, bitmap, nil) + if err != nil { + globalApplication.error("unable to set bitmap on menu item: %w", err) + return + } + m.update() +} + +func newMenuItemImpl(item *MenuItem, parentMenu w32.HMENU, ID int) *windowsMenuItem { + result := &windowsMenuItem{ + menuItem: item, + hMenu: parentMenu, + id: ID, + disabled: item.disabled, + checked: item.checked, + itemType: item.itemType, + label: item.label, + hidden: item.hidden, + } + + return result +} + +func (m *windowsMenuItem) setTooltip(_ string) { + // Unsupported +} + +func (m *windowsMenuItem) getMenuInfo() *w32.MENUITEMINFO { + var mii w32.MENUITEMINFO + mii.CbSize = uint32(unsafe.Sizeof(mii)) + mii.FMask = w32.MIIM_FTYPE | w32.MIIM_ID | w32.MIIM_STATE | w32.MIIM_STRING + if m.IsSeparator() { + mii.FType = w32.MFT_SEPARATOR + } else { + mii.FType = w32.MFT_STRING + if m.IsRadio() { + mii.FType |= w32.MFT_RADIOCHECK + } + thisText := m.label + if m.menuItem.accelerator != nil { + thisText += "\t" + m.menuItem.accelerator.String() + } + mii.DwTypeData = w32.MustStringToUTF16Ptr(thisText) + mii.Cch = uint32(len([]rune(thisText))) + } + mii.WID = uint32(m.id) + if m.Enabled() { + mii.FState &^= w32.MFS_DISABLED + } else { + mii.FState |= w32.MFS_DISABLED + } + + if m.IsCheckbox() || m.IsRadio() { + mii.FMask |= w32.MIIM_CHECKMARKS + } + if m.Checked() { + mii.FState |= w32.MFS_CHECKED + } + + if m.menuItem.submenu != nil { + mii.FMask |= w32.MIIM_SUBMENU + mii.HSubMenu = m.submenu + } + return &mii +} diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go new file mode 100644 index 000000000..5dd1d0f70 --- /dev/null +++ b/v3/pkg/application/messageprocessor.go @@ -0,0 +1,185 @@ +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "net/http" + "strconv" + "sync" + "math" +) + +// TODO maybe we could use a new struct that has the targetWindow as an attribute so we could get rid of passing the targetWindow +// as parameter through every function call. + +const ( + callRequest = 0 + clipboardRequest = 1 + applicationRequest = 2 + eventsRequest = 3 + contextMenuRequest = 4 + dialogRequest = 5 + windowRequest = 6 + screensRequest = 7 + systemRequest = 8 + browserRequest = 9 + cancelCallRequesst = 10 +) + +type MessageProcessor struct { + logger *slog.Logger + + runningCalls map[string]context.CancelFunc + l sync.Mutex +} + +func NewMessageProcessor(logger *slog.Logger) *MessageProcessor { + return &MessageProcessor{ + logger: logger, + runningCalls: map[string]context.CancelFunc{}, + } +} + +func (m *MessageProcessor) httpError(rw http.ResponseWriter, message string, err error) { + m.Error(message, "error", err) + rw.WriteHeader(http.StatusUnprocessableEntity) + _, err = rw.Write([]byte(err.Error())) + if err != nil { + m.Error("Unable to write error response:", "error", err) + } +} + +func (m *MessageProcessor) getTargetWindow(r *http.Request) (Window, string) { + windowName := r.Header.Get(webViewRequestHeaderWindowName) + if windowName != "" { + window, _ := globalApplication.Window.GetByName(windowName) + return window, windowName + } + windowID := r.Header.Get(webViewRequestHeaderWindowId) + if windowID == "" { + return nil, windowID + } + wID, err := strconv.ParseUint(windowID, 10, 64) + if err != nil { + m.Error("Window ID not parsable:", "id", windowID, "error", err) + return nil, windowID + } + // Check if wID is within the valid range for uint + if wID > math.MaxUint32 { + m.Error("Window ID out of range for uint:", "id", wID) + return nil, windowID + } + targetWindow, _ := globalApplication.Window.GetByID(uint(wID)) + if targetWindow == nil { + m.Error("Window ID not found:", "id", wID) + return nil, windowID + } + return targetWindow, windowID +} + +func (m *MessageProcessor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + object := r.URL.Query().Get("object") + if object == "" { + m.httpError(rw, "Invalid runtime call:", errors.New("missing object value")) + return + } + + m.HandleRuntimeCallWithIDs(rw, r) +} + +func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *http.Request) { + defer func() { + if handlePanic() { + rw.WriteHeader(http.StatusInternalServerError) + } + }() + object, err := strconv.Atoi(r.URL.Query().Get("object")) + if err != nil { + m.httpError(rw, "Invalid runtime call:", fmt.Errorf("error decoding object value: %w", err)) + return + } + method, err := strconv.Atoi(r.URL.Query().Get("method")) + if err != nil { + m.httpError(rw, "Invalid runtime call:", fmt.Errorf("error decoding method value: %w", err)) + return + } + params := QueryParams(r.URL.Query()) + + targetWindow, nameOrID := m.getTargetWindow(r) + if targetWindow == nil { + m.httpError(rw, "Invalid runtime call:", fmt.Errorf("window '%s' not found", nameOrID)) + return + } + + switch object { + case windowRequest: + m.processWindowMethod(method, rw, r, targetWindow, params) + case clipboardRequest: + m.processClipboardMethod(method, rw, r, targetWindow, params) + case dialogRequest: + m.processDialogMethod(method, rw, r, targetWindow, params) + case eventsRequest: + m.processEventsMethod(method, rw, r, targetWindow, params) + case applicationRequest: + m.processApplicationMethod(method, rw, r, targetWindow, params) + case contextMenuRequest: + m.processContextMenuMethod(method, rw, r, targetWindow, params) + case screensRequest: + m.processScreensMethod(method, rw, r, targetWindow, params) + case callRequest: + m.processCallMethod(method, rw, r, targetWindow, params) + case systemRequest: + m.processSystemMethod(method, rw, r, targetWindow, params) + case browserRequest: + m.processBrowserMethod(method, rw, r, targetWindow, params) + case cancelCallRequesst: + m.processCallCancelMethod(method, rw, r, targetWindow, params) + default: + m.httpError(rw, "Invalid runtime call:", fmt.Errorf("unknown object %d", object)) + } +} + +func (m *MessageProcessor) Error(message string, args ...any) { + m.logger.Error(message, args...) +} + +func (m *MessageProcessor) Info(message string, args ...any) { + m.logger.Info(message, args...) +} + +func (m *MessageProcessor) json(rw http.ResponseWriter, data any) { + rw.Header().Set("Content-Type", "application/json") + // convert data to json + var jsonPayload = []byte("{}") + var err error + if data != nil { + jsonPayload, err = json.Marshal(data) + if err != nil { + m.Error("Unable to convert data to JSON. Please report this to the Wails team!", "error", err) + return + } + } + _, err = rw.Write(jsonPayload) + if err != nil { + m.Error("Unable to write json payload. Please report this to the Wails team!", "error", err) + return + } + m.ok(rw) +} + +func (m *MessageProcessor) text(rw http.ResponseWriter, data string) { + _, err := rw.Write([]byte(data)) + if err != nil { + m.Error("Unable to write json payload. Please report this to the Wails team!", "error", err) + return + } + rw.Header().Set("Content-Type", "text/plain") + rw.WriteHeader(http.StatusOK) +} + +func (m *MessageProcessor) ok(rw http.ResponseWriter) { + rw.WriteHeader(http.StatusOK) +} diff --git a/v3/pkg/application/messageprocessor_application.go b/v3/pkg/application/messageprocessor_application.go new file mode 100644 index 000000000..fe7cd201f --- /dev/null +++ b/v3/pkg/application/messageprocessor_application.go @@ -0,0 +1,37 @@ +package application + +import ( + "fmt" + "net/http" +) + +const ( + ApplicationHide = 0 + ApplicationShow = 1 + ApplicationQuit = 2 +) + +var applicationMethodNames = map[int]string{ + ApplicationQuit: "Quit", + ApplicationHide: "Hide", + ApplicationShow: "Show", +} + +func (m *MessageProcessor) processApplicationMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + switch method { + case ApplicationQuit: + globalApplication.Quit() + m.ok(rw) + case ApplicationHide: + globalApplication.Hide() + m.ok(rw) + case ApplicationShow: + globalApplication.Show() + m.ok(rw) + default: + m.httpError(rw, "Invalid application call:", fmt.Errorf("unknown method: %d", method)) + return + } + + m.Info("Runtime call:", "method", "Application."+applicationMethodNames[method]) +} diff --git a/v3/pkg/application/messageprocessor_browser.go b/v3/pkg/application/messageprocessor_browser.go new file mode 100644 index 000000000..469369b13 --- /dev/null +++ b/v3/pkg/application/messageprocessor_browser.go @@ -0,0 +1,46 @@ +package application + +import ( + "errors" + "fmt" + "net/http" + + "github.com/pkg/browser" +) + +const ( + BrowserOpenURL = 0 +) + +var browserMethods = map[int]string{ + BrowserOpenURL: "OpenURL", +} + +func (m *MessageProcessor) processBrowserMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid browser call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + switch method { + case BrowserOpenURL: + url := args.String("url") + if url == nil { + m.httpError(rw, "Invalid browser call:", errors.New("missing argument 'url'")) + return + } + + err := browser.OpenURL(*url) + if err != nil { + m.httpError(rw, "OpenURL failed:", err) + return + } + + m.ok(rw) + m.Info("Runtime call:", "method", "Browser."+browserMethods[method], "url", *url) + default: + m.httpError(rw, "Invalid browser call:", fmt.Errorf("unknown method: %d", method)) + return + } +} diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go new file mode 100644 index 000000000..dc25d375c --- /dev/null +++ b/v3/pkg/application/messageprocessor_call.go @@ -0,0 +1,195 @@ +package application + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" +) + +type contextKey string + +const ( + CallBinding = 0 + WindowKey contextKey = "Window" +) + +func (m *MessageProcessor) callErrorCallback(window Window, message string, callID *string, err error) { + m.Error(message, "id", *callID, "error", err) + if cerr := (*CallError)(nil); errors.As(err, &cerr) { + if data, jsonErr := json.Marshal(cerr); jsonErr == nil { + window.CallError(*callID, string(data), true) + return + } else { + m.Error("Unable to convert data to JSON. Please report this to the Wails team!", "id", *callID, "error", jsonErr) + } + } + + window.CallError(*callID, err.Error(), false) +} + +func (m *MessageProcessor) callCallback(window Window, callID *string, result string) { + window.CallResponse(*callID, result) +} + +func (m *MessageProcessor) processCallCancelMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + callID := args.String("call-id") + if callID == nil || *callID == "" { + m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'")) + return + } + + var cancel func() + func() { + m.l.Lock() + defer m.l.Unlock() + cancel = m.runningCalls[*callID] + }() + + if cancel != nil { + cancel() + m.Info("Binding call canceled:", "id", *callID) + } + m.ok(rw) +} + +func (m *MessageProcessor) processCallMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + callID := args.String("call-id") + if callID == nil || *callID == "" { + m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'")) + return + } + + switch method { + case CallBinding: + var options CallOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Invalid binding call:", fmt.Errorf("error parsing call options: %w", err)) + return + } + + ctx, cancel := context.WithCancel(context.WithoutCancel(r.Context())) + + // Schedule cancel in case panics happen before starting the call. + cancelRequired := true + defer func() { + if cancelRequired { + cancel() + } + }() + + ambiguousID := false + func() { + m.l.Lock() + defer m.l.Unlock() + + if m.runningCalls[*callID] != nil { + ambiguousID = true + } else { + m.runningCalls[*callID] = cancel + } + }() + + if ambiguousID { + m.httpError(rw, "Invalid binding call:", fmt.Errorf("ambiguous call id: %s", *callID)) + return + } + + m.ok(rw) // From now on, failures are reported through the error callback. + + // Log call + var methodRef any = options.MethodName + if options.MethodName == "" { + methodRef = options.MethodID + } + m.Info("Binding call started:", "id", *callID, "method", methodRef) + + go func() { + defer handlePanic() + defer func() { + m.l.Lock() + defer m.l.Unlock() + delete(m.runningCalls, *callID) + }() + defer cancel() + + var boundMethod *BoundMethod + if options.MethodName != "" { + boundMethod = globalApplication.bindings.Get(&options) + if boundMethod == nil { + m.callErrorCallback(window, "Binding call failed:", callID, &CallError{ + Kind: ReferenceError, + Message: fmt.Sprintf("unknown bound method name '%s'", options.MethodName), + }) + return + } + } else { + boundMethod = globalApplication.bindings.GetByID(options.MethodID) + if boundMethod == nil { + m.callErrorCallback(window, "Binding call failed:", callID, &CallError{ + Kind: ReferenceError, + Message: fmt.Sprintf("unknown bound method id %d", options.MethodID), + }) + return + } + } + + // Prepare args for logging. This should never fail since json.Unmarshal succeeded before. + jsonArgs, _ := json.Marshal(options.Args) + var jsonResult []byte + defer func() { + m.Info("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult)) + }() + + // Set the context values for the window + if window != nil { + ctx = context.WithValue(ctx, WindowKey, window) + } + + result, err := boundMethod.Call(ctx, options.Args) + if cerr := (*CallError)(nil); errors.As(err, &cerr) { + switch cerr.Kind { + case ReferenceError, TypeError: + m.callErrorCallback(window, "Binding call failed:", callID, cerr) + case RuntimeError: + m.callErrorCallback(window, "Bound method returned an error:", callID, cerr) + } + return + } + + if result != nil { + // convert result to json + jsonResult, err = json.Marshal(result) + if err != nil { + m.callErrorCallback(window, "Binding call failed:", callID, &CallError{ + Kind: TypeError, + Message: fmt.Sprintf("error marshaling result: %s", err), + }) + return + } + } + + m.callCallback(window, callID, string(jsonResult)) + }() + + cancelRequired = false + + default: + m.httpError(rw, "Invalid binding call:", fmt.Errorf("unknown method: %d", method)) + return + } +} diff --git a/v3/pkg/application/messageprocessor_clipboard.go b/v3/pkg/application/messageprocessor_clipboard.go new file mode 100644 index 000000000..d77b6459d --- /dev/null +++ b/v3/pkg/application/messageprocessor_clipboard.go @@ -0,0 +1,47 @@ +package application + +import ( + "errors" + "fmt" + "net/http" +) + +const ( + ClipboardSetText = 0 + ClipboardText = 1 +) + +var clipboardMethods = map[int]string{ + ClipboardSetText: "SetText", + ClipboardText: "Text", +} + +func (m *MessageProcessor) processClipboardMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid clipboard call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + var text string + + switch method { + case ClipboardSetText: + textp := args.String("text") + if textp == nil { + m.httpError(rw, "Invalid clipboard call:", errors.New("missing argument 'text'")) + return + } + text = *textp + globalApplication.Clipboard.SetText(text) + m.ok(rw) + case ClipboardText: + text, _ = globalApplication.Clipboard.Text() + m.text(rw, text) + default: + m.httpError(rw, "Invalid clipboard call:", fmt.Errorf("unknown method: %d", method)) + return + } + + m.Info("Runtime call:", "method", "Clipboard."+clipboardMethods[method], "text", text) +} diff --git a/v3/pkg/application/messageprocessor_contextmenu.go b/v3/pkg/application/messageprocessor_contextmenu.go new file mode 100644 index 000000000..c315db15a --- /dev/null +++ b/v3/pkg/application/messageprocessor_contextmenu.go @@ -0,0 +1,50 @@ +package application + +import ( + "fmt" + "net/http" +) + +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data string `json:"data"` +} + +func (d ContextMenuData) clone() *ContextMenuData { + return &ContextMenuData{ + Id: d.Id, + X: d.X, + Y: d.Y, + Data: d.Data, + } +} + +const ( + ContextMenuOpen = 0 +) + +var contextmenuMethodNames = map[int]string{ + ContextMenuOpen: "Open", +} + +func (m *MessageProcessor) processContextMenuMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) { + switch method { + case ContextMenuOpen: + var data ContextMenuData + err := params.ToStruct(&data) + if err != nil { + m.httpError(rw, "Invalid contextmenu call:", fmt.Errorf("error parsing parameters: %w", err)) + return + } + + window.OpenContextMenu(&data) + + m.ok(rw) + m.Info("Runtime call:", "method", "ContextMenu."+contextmenuMethodNames[method], "id", data.Id, "x", data.X, "y", data.Y, "data", data.Data) + default: + m.httpError(rw, "Invalid contextmenu call:", fmt.Errorf("unknown method: %d", method)) + return + } +} diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go new file mode 100644 index 000000000..772f25524 --- /dev/null +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -0,0 +1,168 @@ +package application + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "runtime" +) + +const ( + DialogInfo = 0 + DialogWarning = 1 + DialogError = 2 + DialogQuestion = 3 + DialogOpenFile = 4 + DialogSaveFile = 5 +) + +var dialogMethodNames = map[int]string{ + DialogInfo: "Info", + DialogWarning: "Warning", + DialogError: "Error", + DialogQuestion: "Question", + DialogOpenFile: "OpenFile", + DialogSaveFile: "SaveFile", +} + +func (m *MessageProcessor) dialogErrorCallback(window Window, message string, dialogID *string, err error) { + m.Error(message, "error", err) + window.DialogError(*dialogID, err.Error()) +} + +func (m *MessageProcessor) dialogCallback(window Window, dialogID *string, result string, isJSON bool) { + window.DialogResponse(*dialogID, result, isJSON) +} + +func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid dialog call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + dialogID := args.String("dialog-id") + if dialogID == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing argument 'dialog-id'")) + return + } + + var methodName = "Dialog." + dialogMethodNames[method] + + switch method { + case DialogInfo, DialogWarning, DialogError, DialogQuestion: + var options MessageDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err)) + return + } + if len(options.Buttons) == 0 { + switch runtime.GOOS { + case "darwin": + options.Buttons = []*Button{{Label: "OK", IsDefault: true}} + } + } + var dialog *MessageDialog + switch method { + case DialogInfo: + dialog = InfoDialog() + case DialogWarning: + dialog = WarningDialog() + case DialogError: + dialog = ErrorDialog() + case DialogQuestion: + dialog = QuestionDialog() + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + dialog.AttachToWindow(window) + } + + dialog.SetTitle(options.Title) + dialog.SetMessage(options.Message) + for _, button := range options.Buttons { + label := button.Label + button.OnClick(func() { + m.dialogCallback(window, dialogID, label, false) + }) + } + dialog.AddButtons(options.Buttons) + dialog.Show() + m.ok(rw) + m.Info("Runtime call:", "method", methodName, "options", options) + + case DialogOpenFile: + var options OpenFileDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err)) + return + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window.(*WebviewWindow) + } + dialog := globalApplication.Dialog.OpenFileWithOptions(&options) + + go func() { + defer handlePanic() + if options.AllowsMultipleSelection { + files, err := dialog.PromptForMultipleSelection() + if err != nil { + m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error getting selection: %w", err)) + return + } else { + result, err := json.Marshal(files) + if err != nil { + m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error marshaling files: %w", err)) + return + } + m.dialogCallback(window, dialogID, string(result), true) + m.Info("Runtime call:", "method", methodName, "result", result) + } + } else { + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error getting selection: %w", err)) + return + } + m.dialogCallback(window, dialogID, file, false) + m.Info("Runtime call:", "method", methodName, "result", file) + } + }() + m.ok(rw) + m.Info("Runtime call:", "method", methodName, "options", options) + + case DialogSaveFile: + var options SaveFileDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err)) + return + } + var detached = args.Bool("Detached") + if detached == nil || !*detached { + options.Window = window.(*WebviewWindow) + } + dialog := globalApplication.Dialog.SaveFileWithOptions(&options) + + go func() { + defer handlePanic() + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Dialog.SaveFile failed", dialogID, fmt.Errorf("error getting selection: %w", err)) + return + } + m.dialogCallback(window, dialogID, file, false) + m.Info("Runtime call:", "method", methodName, "result", file) + }() + m.ok(rw) + m.Info("Runtime call:", "method", methodName, "options", options) + + default: + m.httpError(rw, "Invalid dialog call:", fmt.Errorf("unknown method: %d", method)) + return + } +} diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go new file mode 100644 index 000000000..93ab34064 --- /dev/null +++ b/v3/pkg/application/messageprocessor_events.go @@ -0,0 +1,41 @@ +package application + +import ( + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +const ( + EventsEmit = 0 +) + +var eventsMethodNames = map[int]string{ + EventsEmit: "Emit", +} + +func (m *MessageProcessor) processEventsMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) { + switch method { + case EventsEmit: + var event CustomEvent + err := params.ToStruct(&event) + if err != nil { + m.httpError(rw, "Invalid events call:", fmt.Errorf("error parsing event: %w", err)) + return + } + if event.Name == "" { + m.httpError(rw, "Invalid events call:", errors.New("missing event name")) + return + } + + event.Sender = window.Name() + globalApplication.Event.EmitEvent(&event) + + m.ok(rw) + m.Info("Runtime call:", "method", "Events."+eventsMethodNames[method], "name", event.Name, "sender", event.Sender, "data", event.Data, "cancelled", event.IsCancelled()) + default: + m.httpError(rw, "Invalid events call:", fmt.Errorf("unknown method: %d", method)) + return + } +} diff --git a/v3/pkg/application/messageprocessor_params.go b/v3/pkg/application/messageprocessor_params.go new file mode 100644 index 000000000..b4f313a3a --- /dev/null +++ b/v3/pkg/application/messageprocessor_params.go @@ -0,0 +1,206 @@ +package application + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type QueryParams map[string][]string + +func (qp QueryParams) String(key string) *string { + if qp == nil { + return nil + } + values := qp[key] + if len(values) == 0 { + return nil + } + return &values[0] +} + +func (qp QueryParams) Int(key string) *int { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.Atoi(*val) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) UInt8(key string) *uint8 { + val := qp.String(key) + if val == nil { + return nil + } + intResult, err := strconv.Atoi(*val) + if err != nil { + return nil + } + + if intResult < 0 { + intResult = 0 + } + if intResult > 255 { + intResult = 255 + } + + var result = uint8(intResult) + + return &result +} +func (qp QueryParams) UInt(key string) *uint { + val := qp.String(key) + if val == nil { + return nil + } + intResult, err := strconv.Atoi(*val) + if err != nil { + return nil + } + + if intResult < 0 { + intResult = 0 + } + if intResult > 255 { + intResult = 255 + } + + var result = uint(intResult) + + return &result +} + +func (qp QueryParams) Bool(key string) *bool { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.ParseBool(*val) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) Float64(key string) *float64 { + val := qp.String(key) + if val == nil { + return nil + } + result, err := strconv.ParseFloat(*val, 64) + if err != nil { + return nil + } + return &result +} + +func (qp QueryParams) ToStruct(str any) error { + args := qp["args"] + if len(args) == 1 { + return json.Unmarshal([]byte(args[0]), &str) + } + return nil +} + +type Args struct { + data map[string]any +} + +func (a *Args) String(key string) *string { + if a == nil { + return nil + } + if val := a.data[key]; val != nil { + result := fmt.Sprintf("%v", val) + return &result + } + return nil +} + +func (a *Args) Int(s string) *int { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[int](val) + } + return nil +} + +func convertNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](val any) *T { + if val == nil { + return nil + } + var result T + switch v := val.(type) { + case T: + result = v + case float64: + result = T(v) + default: + return nil + } + return &result +} + +func (a *Args) UInt8(s string) *uint8 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint8](val) + } + return nil +} + +func (a *Args) UInt(s string) *uint { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + return convertNumber[uint](val) + } + return nil +} + +func (a *Args) Float64(s string) *float64 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + if result, ok := val.(float64); ok { + return &result + } + } + return nil +} + +func (a *Args) Bool(s string) *bool { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + if result, ok := val.(bool); ok { + return &result + } + } + return nil +} + +func (qp QueryParams) Args() (*Args, error) { + argData := qp["args"] + var result = &Args{ + data: make(map[string]any), + } + if len(argData) == 1 { + err := json.Unmarshal([]byte(argData[0]), &result.data) + if err != nil { + return nil, err + } + } + return result, nil +} diff --git a/v3/pkg/application/messageprocessor_screens.go b/v3/pkg/application/messageprocessor_screens.go new file mode 100644 index 000000000..d90487d59 --- /dev/null +++ b/v3/pkg/application/messageprocessor_screens.go @@ -0,0 +1,42 @@ +package application + +import ( + "fmt" + "net/http" +) + +const ( + ScreensGetAll = 0 + ScreensGetPrimary = 1 + ScreensGetCurrent = 2 +) + +var screensMethodNames = map[int]string{ + ScreensGetAll: "GetAll", + ScreensGetPrimary: "GetPrimary", + ScreensGetCurrent: "GetCurrent", +} + +func (m *MessageProcessor) processScreensMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, _ QueryParams) { + switch method { + case ScreensGetAll: + screens := globalApplication.Screen.GetAll() + m.json(rw, screens) + case ScreensGetPrimary: + screen := globalApplication.Screen.GetPrimary() + m.json(rw, screen) + case ScreensGetCurrent: + screen, err := globalApplication.Window.Current().GetScreen() + if err != nil { + m.httpError(rw, "Window.GetScreen failed:", err) + return + } + m.json(rw, screen) + default: + m.httpError(rw, "Invalid screens call:", fmt.Errorf("unknown method: %d", method)) + return + } + + m.Info("Runtime call:", "method", "Screens."+screensMethodNames[method]) + +} diff --git a/v3/pkg/application/messageprocessor_system.go b/v3/pkg/application/messageprocessor_system.go new file mode 100644 index 000000000..e1a1e771c --- /dev/null +++ b/v3/pkg/application/messageprocessor_system.go @@ -0,0 +1,30 @@ +package application + +import ( + "fmt" + "net/http" +) + +const ( + SystemIsDarkMode = 0 + Environment = 1 +) + +var systemMethodNames = map[int]string{ + SystemIsDarkMode: "IsDarkMode", + Environment: "Environment", +} + +func (m *MessageProcessor) processSystemMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) { + switch method { + case SystemIsDarkMode: + m.json(rw, globalApplication.Env.IsDarkMode()) + case Environment: + m.json(rw, globalApplication.Env.Info()) + default: + m.httpError(rw, "Invalid system call:", fmt.Errorf("unknown method: %d", method)) + return + } + + m.Info("Runtime call:", "method", "System."+systemMethodNames[method]) +} diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go new file mode 100644 index 000000000..6b9f2a9b2 --- /dev/null +++ b/v3/pkg/application/messageprocessor_window.go @@ -0,0 +1,430 @@ +package application + +import ( + "errors" + "fmt" + "net/http" +) + +const ( + WindowPosition = 0 + WindowCenter = 1 + WindowClose = 2 + WindowDisableSizeConstraints = 3 + WindowEnableSizeConstraints = 4 + WindowFocus = 5 + WindowForceReload = 6 + WindowFullscreen = 7 + WindowGetScreen = 8 + WindowGetZoom = 9 + WindowHeight = 10 + WindowHide = 11 + WindowIsFocused = 12 + WindowIsFullscreen = 13 + WindowIsMaximised = 14 + WindowIsMinimised = 15 + WindowMaximise = 16 + WindowMinimise = 17 + WindowName = 18 + WindowOpenDevTools = 19 + WindowRelativePosition = 20 + WindowReload = 21 + WindowResizable = 22 + WindowRestore = 23 + WindowSetPosition = 24 + WindowSetAlwaysOnTop = 25 + WindowSetBackgroundColour = 26 + WindowSetFrameless = 27 + WindowSetFullscreenButtonEnabled = 28 + WindowSetMaxSize = 29 + WindowSetMinSize = 30 + WindowSetRelativePosition = 31 + WindowSetResizable = 32 + WindowSetSize = 33 + WindowSetTitle = 34 + WindowSetZoom = 35 + WindowShow = 36 + WindowSize = 37 + WindowToggleFullscreen = 38 + WindowToggleMaximise = 39 + WindowToggleFrameless = 40 + WindowUnFullscreen = 41 + WindowUnMaximise = 42 + WindowUnMinimise = 43 + WindowWidth = 44 + WindowZoom = 45 + WindowZoomIn = 46 + WindowZoomOut = 47 + WindowZoomReset = 48 +) + +var windowMethodNames = map[int]string{ + WindowPosition: "Position", + WindowCenter: "Center", + WindowClose: "Close", + WindowDisableSizeConstraints: "DisableSizeConstraints", + WindowEnableSizeConstraints: "EnableSizeConstraints", + WindowFocus: "Focus", + WindowForceReload: "ForceReload", + WindowFullscreen: "Fullscreen", + WindowGetScreen: "GetScreen", + WindowGetZoom: "GetZoom", + WindowHeight: "Height", + WindowHide: "Hide", + WindowIsFocused: "IsFocused", + WindowIsFullscreen: "IsFullscreen", + WindowIsMaximised: "IsMaximised", + WindowIsMinimised: "IsMinimised", + WindowMaximise: "Maximise", + WindowMinimise: "Minimise", + WindowName: "Name", + WindowOpenDevTools: "OpenDevTools", + WindowRelativePosition: "RelativePosition", + WindowReload: "Reload", + WindowResizable: "Resizable", + WindowRestore: "Restore", + WindowSetPosition: "SetPosition", + WindowSetAlwaysOnTop: "SetAlwaysOnTop", + WindowSetBackgroundColour: "SetBackgroundColour", + WindowSetFrameless: "SetFrameless", + WindowSetFullscreenButtonEnabled: "SetFullscreenButtonEnabled", + WindowSetMaxSize: "SetMaxSize", + WindowSetMinSize: "SetMinSize", + WindowSetRelativePosition: "SetRelativePosition", + WindowSetResizable: "SetResizable", + WindowSetSize: "SetSize", + WindowSetTitle: "SetTitle", + WindowSetZoom: "SetZoom", + WindowShow: "Show", + WindowSize: "Size", + WindowToggleFullscreen: "ToggleFullscreen", + WindowToggleMaximise: "ToggleMaximise", + WindowToggleFrameless: "ToggleFrameless", + WindowUnFullscreen: "UnFullscreen", + WindowUnMaximise: "UnMaximise", + WindowUnMinimise: "UnMinimise", + WindowWidth: "Width", + WindowZoom: "Zoom", + WindowZoomIn: "ZoomIn", + WindowZoomOut: "ZoomOut", + WindowZoomReset: "ZoomReset", +} + +func (m *MessageProcessor) processWindowMethod( + method int, + rw http.ResponseWriter, + _ *http.Request, + window Window, + params QueryParams, +) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Invalid window call:", fmt.Errorf("unable to parse arguments: %w", err)) + return + } + + switch method { + case WindowPosition: + x, y := window.Position() + m.json(rw, map[string]interface{}{ + "x": x, + "y": y, + }) + case WindowCenter: + window.Center() + m.ok(rw) + case WindowClose: + window.Close() + m.ok(rw) + case WindowDisableSizeConstraints: + window.DisableSizeConstraints() + m.ok(rw) + case WindowEnableSizeConstraints: + window.EnableSizeConstraints() + m.ok(rw) + case WindowFocus: + window.Focus() + m.ok(rw) + case WindowForceReload: + window.ForceReload() + m.ok(rw) + case WindowFullscreen: + window.Fullscreen() + m.ok(rw) + case WindowGetScreen: + screen, err := window.GetScreen() + if err != nil { + m.httpError(rw, "Window.GetScreen failed:", err) + return + } + m.json(rw, screen) + case WindowGetZoom: + zoom := window.GetZoom() + m.json(rw, zoom) + case WindowHeight: + height := window.Height() + m.json(rw, height) + case WindowHide: + window.Hide() + m.ok(rw) + case WindowIsFocused: + isFocused := window.IsFocused() + m.json(rw, isFocused) + case WindowIsFullscreen: + isFullscreen := window.IsFullscreen() + m.json(rw, isFullscreen) + case WindowIsMaximised: + isMaximised := window.IsMaximised() + m.json(rw, isMaximised) + case WindowIsMinimised: + isMinimised := window.IsMinimised() + m.json(rw, isMinimised) + case WindowMaximise: + window.Maximise() + m.ok(rw) + case WindowMinimise: + window.Minimise() + m.ok(rw) + case WindowName: + name := window.Name() + m.json(rw, name) + case WindowRelativePosition: + x, y := window.RelativePosition() + m.json(rw, map[string]interface{}{ + "x": x, + "y": y, + }) + case WindowReload: + window.Reload() + m.ok(rw) + case WindowResizable: + resizable := window.Resizable() + m.json(rw, resizable) + case WindowRestore: + window.Restore() + m.ok(rw) + case WindowSetPosition: + x := args.Int("x") + if x == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'x'")) + return + } + y := args.Int("y") + if y == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'y'")) + return + } + window.SetPosition(*x, *y) + m.ok(rw) + case WindowSetAlwaysOnTop: + alwaysOnTop := args.Bool("alwaysOnTop") + if alwaysOnTop == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'alwaysOnTop'"), + ) + return + } + window.SetAlwaysOnTop(*alwaysOnTop) + m.ok(rw) + case WindowSetBackgroundColour: + r := args.UInt8("r") + if r == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'r'")) + return + } + g := args.UInt8("g") + if g == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'g'")) + return + } + b := args.UInt8("b") + if b == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'b'")) + return + } + a := args.UInt8("a") + if a == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'a'")) + return + } + window.SetBackgroundColour(RGBA{ + Red: *r, + Green: *g, + Blue: *b, + Alpha: *a, + }) + m.ok(rw) + case WindowSetFrameless: + frameless := args.Bool("frameless") + if frameless == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'frameless'"), + ) + return + } + window.SetFrameless(*frameless) + m.ok(rw) + case WindowSetMaxSize: + width := args.Int("width") + if width == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'width'"), + ) + return + } + height := args.Int("height") + if height == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'height'"), + ) + return + } + window.SetMaxSize(*width, *height) + m.ok(rw) + case WindowSetMinSize: + width := args.Int("width") + if width == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'width'"), + ) + return + } + height := args.Int("height") + if height == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'height'"), + ) + return + } + window.SetMinSize(*width, *height) + m.ok(rw) + case WindowSetRelativePosition: + x := args.Int("x") + if x == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'x'")) + return + } + y := args.Int("y") + if y == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'y'")) + return + } + window.SetRelativePosition(*x, *y) + m.ok(rw) + case WindowSetResizable: + resizable := args.Bool("resizable") + if resizable == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'resizable'"), + ) + return + } + window.SetResizable(*resizable) + m.ok(rw) + case WindowSetSize: + width := args.Int("width") + if width == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'width'"), + ) + return + } + height := args.Int("height") + if height == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'height'"), + ) + return + } + window.SetSize(*width, *height) + m.ok(rw) + case WindowSetTitle: + title := args.String("title") + if title == nil { + m.httpError(rw, "Invalid window call:", errors.New("missing argument 'title'")) + return + } + window.SetTitle(*title) + m.ok(rw) + case WindowSetZoom: + zoom := args.Float64("zoom") + if zoom == nil { + m.httpError( + rw, + "Invalid window call:", + errors.New("missing or invalid argument 'zoom'"), + ) + return + } + window.SetZoom(*zoom) + m.ok(rw) + case WindowShow: + window.Show() + m.ok(rw) + case WindowSize: + width, height := window.Size() + m.json(rw, map[string]interface{}{ + "width": width, + "height": height, + }) + case WindowOpenDevTools: + window.OpenDevTools() + m.ok(rw) + case WindowToggleFullscreen: + window.ToggleFullscreen() + m.ok(rw) + case WindowToggleMaximise: + window.ToggleMaximise() + m.ok(rw) + case WindowToggleFrameless: + window.ToggleFrameless() + m.ok(rw) + case WindowUnFullscreen: + window.UnFullscreen() + m.ok(rw) + case WindowUnMaximise: + window.UnMaximise() + m.ok(rw) + case WindowUnMinimise: + window.UnMinimise() + m.ok(rw) + case WindowWidth: + width := window.Width() + m.json(rw, width) + case WindowZoom: + window.Zoom() + m.ok(rw) + case WindowZoomIn: + window.ZoomIn() + m.ok(rw) + case WindowZoomOut: + window.ZoomOut() + m.ok(rw) + case WindowZoomReset: + window.ZoomReset() + m.ok(rw) + default: + m.httpError(rw, "Invalid window call:", fmt.Errorf("unknown method %d", method)) + return + } + + m.Info("Runtime call:", "method", "Window."+windowMethodNames[method]) +} diff --git a/v3/pkg/application/panic_handler.go b/v3/pkg/application/panic_handler.go new file mode 100644 index 000000000..53f42a309 --- /dev/null +++ b/v3/pkg/application/panic_handler.go @@ -0,0 +1,104 @@ +package application + +import ( + "fmt" + "runtime" + "runtime/debug" + "strings" + "time" +) + +func getStackTrace(skipStart int, skipEnd int) string { + // Get all program counters first + pc := make([]uintptr, 32) + n := runtime.Callers(skipStart+1, pc) + if n == 0 { + return "" + } + + pc = pc[:n] + frames := runtime.CallersFrames(pc) + + // Collect all frames first + var allFrames []runtime.Frame + for { + frame, more := frames.Next() + allFrames = append(allFrames, frame) + if !more { + break + } + } + + // Remove frames from the end + if len(allFrames) > skipEnd { + allFrames = allFrames[:len(allFrames)-skipEnd] + } + + // Build the output string + var builder strings.Builder + for _, frame := range allFrames { + fmt.Fprintf(&builder, "%s\n\tat %s:%d\n", + frame.Function, frame.File, frame.Line) + } + return builder.String() +} + +type handlePanicOptions struct { + skipEnd int +} + +type PanicDetails struct { + StackTrace string + Error error + Time time.Time + FullStackTrace string +} + +func newPanicDetails(err error, trace string) *PanicDetails { + return &PanicDetails{ + Error: err, + Time: time.Now(), + StackTrace: trace, + FullStackTrace: string(debug.Stack()), + } +} + +// handlePanic handles any panics +// Returns the error if there was one +func handlePanic(options ...handlePanicOptions) bool { + // Try to recover + e := recover() + if e == nil { + return false + } + + // Get the error + err, ok := e.(error) + if !ok { + err = fmt.Errorf("%v", e) + } + + // Get the stack trace + var stackTrace string + skipEnd := 0 + if len(options) > 0 { + skipEnd = options[0].skipEnd + } + stackTrace = getStackTrace(3, skipEnd) + + processPanic(newPanicDetails(err, stackTrace)) + return false +} + +func processPanic(panicDetails *PanicDetails) { + h := globalApplication.options.PanicHandler + if h != nil { + h(panicDetails) + return + } + defaultPanicHandler(panicDetails) +} + +func defaultPanicHandler(panicDetails *PanicDetails) { + globalApplication.fatal("panic error: %w\n%s", panicDetails.Error, panicDetails.StackTrace) +} diff --git a/v3/pkg/application/path.go b/v3/pkg/application/path.go new file mode 100644 index 000000000..44066fa96 --- /dev/null +++ b/v3/pkg/application/path.go @@ -0,0 +1,133 @@ +package application + +import "github.com/adrg/xdg" + +type PathType int + +const ( + // PathHome is the user's home directory. + PathHome PathType = iota + + // PathDataHome defines the base directory relative to which user-specific + // data files should be stored. This directory is defined by the + // $XDG_DATA_HOME environment variable. If the variable is not set, + // a default equal to $HOME/.local/share should be used. + PathDataHome + + // PathConfigHome defines the base directory relative to which user-specific + // configuration files should be written. This directory is defined by + // the $XDG_CONFIG_HOME environment variable. If the variable is + // not set, a default equal to $HOME/.config should be used. + PathConfigHome + + // PathStateHome defines the base directory relative to which user-specific + // state files should be stored. This directory is defined by the + // $XDG_STATE_HOME environment variable. If the variable is not set, + // a default equal to ~/.local/state should be used. + PathStateHome + + // PathCacheHome defines the base directory relative to which user-specific + // non-essential (cached) data should be written. This directory is + // defined by the $XDG_CACHE_HOME environment variable. If the variable + // is not set, a default equal to $HOME/.cache should be used. + PathCacheHome + + // PathRuntimeDir defines the base directory relative to which user-specific + // non-essential runtime files and other file objects (such as sockets, + // named pipes, etc.) should be stored. This directory is defined by the + // $XDG_RUNTIME_DIR environment variable. If the variable is not set, + // applications should fall back to a replacement directory with similar + // capabilities. Applications should use this directory for communication + // and synchronization purposes and should not place larger files in it, + // since it might reside in runtime memory and cannot necessarily be + // swapped out to disk. + PathRuntimeDir + + // PathDesktop defines the location of the user's desktop directory. + PathDesktop + + // PathDownload defines a suitable location for user downloaded files. + PathDownload + + // PathDocuments defines a suitable location for user document files. + PathDocuments + + // PathMusic defines a suitable location for user audio files. + PathMusic + + // PathPictures defines a suitable location for user image files. + PathPictures + + // PathVideos defines a suitable location for user video files. + PathVideos + + // PathTemplates defines a suitable location for user template files. + PathTemplates + + // PathPublicShare defines a suitable location for user shared files. + PathPublicShare +) + +var paths = map[PathType]string{ + PathHome: xdg.Home, + PathDataHome: xdg.DataHome, + PathConfigHome: xdg.ConfigHome, + PathStateHome: xdg.StateHome, + PathCacheHome: xdg.CacheHome, + PathRuntimeDir: xdg.RuntimeDir, + PathDesktop: xdg.UserDirs.Desktop, + PathDownload: xdg.UserDirs.Download, + PathDocuments: xdg.UserDirs.Documents, + PathMusic: xdg.UserDirs.Music, + PathPictures: xdg.UserDirs.Pictures, + PathVideos: xdg.UserDirs.Videos, + PathTemplates: xdg.UserDirs.Templates, + PathPublicShare: xdg.UserDirs.PublicShare, +} + +type PathTypes int + +const ( + // PathsDataDirs defines the preference-ordered set of base directories to + // search for data files in addition to the DataHome base directory. + // This set of directories is defined by the $XDG_DATA_DIRS environment + // variable. If the variable is not set, the default directories + // to be used are /usr/local/share and /usr/share, in that order. The + // DataHome directory is considered more important than any of the + // directories defined by DataDirs. Therefore, user data files should be + // written relative to the DataHome directory, if possible. + PathsDataDirs PathTypes = iota + + // PathsConfigDirs defines the preference-ordered set of base directories + // search for configuration files in addition to the ConfigHome base + // directory. This set of directories is defined by the $XDG_CONFIG_DIRS + // environment variable. If the variable is not set, a default equal + // to /etc/xdg should be used. The ConfigHome directory is considered + // more important than any of the directories defined by ConfigDirs. + // Therefore, user config files should be written relative to the + // ConfigHome directory, if possible. + PathsConfigDirs + + // PathsFontDirs defines the common locations where font files are stored. + PathsFontDirs + + // PathsApplicationDirs defines the common locations of applications. + PathsApplicationDirs +) + +var pathdirs = map[PathTypes][]string{ + PathsDataDirs: xdg.DataDirs, + PathsConfigDirs: xdg.ConfigDirs, + PathsFontDirs: xdg.FontDirs, + PathsApplicationDirs: xdg.ApplicationDirs, +} + +// Path returns the path for the given selector +func Path(selector PathType) string { + return paths[selector] +} + +// Paths returns the paths for the given selector +func Paths(selector PathTypes) []string { + return pathdirs[selector] +} diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go new file mode 100644 index 000000000..f69abeaa8 --- /dev/null +++ b/v3/pkg/application/popupmenu_windows.go @@ -0,0 +1,303 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/w32" + "unsafe" +) + +const ( + MenuItemMsgID = w32.WM_APP + 1024 +) + +type RadioGroupMember struct { + ID int + MenuItem *MenuItem +} + +type RadioGroup []*RadioGroupMember + +func (r *RadioGroup) Add(id int, item *MenuItem) { + *r = append(*r, &RadioGroupMember{ + ID: id, + MenuItem: item, + }) +} + +func (r *RadioGroup) Bounds() (int, int) { + p := *r + return p[0].ID, p[len(p)-1].ID +} + +func (r *RadioGroup) MenuID(item *MenuItem) int { + for _, member := range *r { + if member.MenuItem == item { + return member.ID + } + } + panic("RadioGroup.MenuID: item not found:") +} + +type Win32Menu struct { + isPopup bool + menu w32.HMENU + parentWindow *windowsWebviewWindow + parent w32.HWND + menuMapping map[int]*MenuItem + checkboxItems map[*MenuItem][]int + radioGroups map[*MenuItem][]*RadioGroup + menuData *Menu + currentMenuID int + onMenuClose func() + onMenuOpen func() +} + +func (p *Win32Menu) newMenu() w32.HMENU { + if p.isPopup { + return w32.NewPopupMenu() + } + return w32.CreateMenu() +} + +func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { + currentRadioGroup := RadioGroup{} + for _, item := range inputMenu.items { + p.currentMenuID++ + itemID := p.currentMenuID + p.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + + if item.Hidden() { + if item.accelerator != nil { + if p.parentWindow != nil { + // Remove the accelerator from the keybindings + p.parentWindow.parent.removeMenuBinding(item.accelerator) + } else { + // Remove the global keybindings + globalApplication.KeyBinding.Remove(item.accelerator.String()) + } + } + } + + flags := uint32(w32.MF_STRING) + if item.disabled { + flags = flags | w32.MF_GRAYED + } + if item.checked { + flags = flags | w32.MF_CHECKED + } + if item.IsSeparator() { + flags = flags | w32.MF_SEPARATOR + } + + if item.checked && item.IsRadio() { + flags = flags | w32.MFT_RADIOCHECK + } + + if item.IsCheckbox() { + p.checkboxItems[item] = append(p.checkboxItems[item], itemID) + } + if item.IsRadio() { + currentRadioGroup.Add(itemID, item) + } else { + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } + } + + if item.submenu != nil { + flags = flags | w32.MF_POPUP + newSubmenu := p.newMenu() + p.buildMenu(newSubmenu, item.submenu) + itemID = int(newSubmenu) + menuItemImpl.submenu = newSubmenu + } + + var menuText = item.Label() + if item.accelerator != nil { + menuText = menuText + "\t" + item.accelerator.String() + if item.callback != nil { + if p.parentWindow != nil { + p.parentWindow.parent.addMenuBinding(item.accelerator, item) + } else { + globalApplication.KeyBinding.Add(item.accelerator.String(), func(w *WebviewWindow) { + item.handleClick() + }) + } + } + } + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + + ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText)) + if !ok { + globalApplication.fatal("error adding menu item '%s'", menuText) + } + if item.bitmap != nil { + err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) + if err != nil { + globalApplication.fatal("error setting menu icons: %w", err) + } + } + } + if len(currentRadioGroup) > 0 { + for _, radioMember := range currentRadioGroup { + currentRadioGroup := currentRadioGroup + p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup) + } + currentRadioGroup = RadioGroup{} + } +} + +func (p *Win32Menu) Update() { + p.menu = p.newMenu() + p.menuMapping = make(map[int]*MenuItem) + p.currentMenuID = MenuItemMsgID + p.buildMenu(p.menu, p.menuData) + p.updateRadioGroups() +} + +func NewPopupMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + isPopup: true, + parent: parent, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} +func NewApplicationMenu(parent *windowsWebviewWindow, inputMenu *Menu) *Win32Menu { + result := &Win32Menu{ + parentWindow: parent, + parent: parent.hwnd, + menuData: inputMenu, + checkboxItems: make(map[*MenuItem][]int), + radioGroups: make(map[*MenuItem][]*RadioGroup), + } + result.Update() + return result +} + +func (p *Win32Menu) ShowAt(x int, y int) { + + w32.SetForegroundWindow(p.parent) + + if p.onMenuOpen != nil { + p.onMenuOpen() + } + + // Get screen dimensions to determine menu positioning + monitor := w32.MonitorFromWindow(p.parent, w32.MONITOR_DEFAULTTONEAREST) + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if !w32.GetMonitorInfo(monitor, &monitorInfo) { + globalApplication.fatal("GetMonitorInfo failed") + } + + // Set flags to always position the menu above the cursor + menuFlags := uint32(w32.TPM_LEFTALIGN | w32.TPM_BOTTOMALIGN) + + // Check if we're close to the right edge of the screen + // If so, right-align the menu with some padding + if x > int(monitorInfo.RcWork.Right)-200 { // Assuming 200px as a reasonable menu width + menuFlags = uint32(w32.TPM_RIGHTALIGN | w32.TPM_BOTTOMALIGN) + // Add a small padding (10px) from the right edge + x = int(monitorInfo.RcWork.Right) - 10 + } + + if !w32.TrackPopupMenuEx(p.menu, menuFlags, int32(x), int32(y), p.parent, nil) { + globalApplication.fatal("TrackPopupMenu failed") + } + + if p.onMenuClose != nil { + p.onMenuClose() + } + + if !w32.PostMessage(p.parent, w32.WM_NULL, 0, 0) { + globalApplication.fatal("PostMessage failed") + } + +} + +func (p *Win32Menu) ShowAtCursor() { + x, y, ok := w32.GetCursorPos() + if ok == false { + globalApplication.fatal("GetCursorPos failed") + } + + p.ShowAt(x, y) +} + +func (p *Win32Menu) ProcessCommand(cmdMsgID int) bool { + item := p.menuMapping[cmdMsgID] + if item == nil { + return false + } + if item.IsRadio() { + if item.checked { + return true + } + item.checked = true + p.updateRadioGroup(item) + } + if item.callback != nil { + item.handleClick() + } + return true +} + +func (p *Win32Menu) Destroy() { + w32.DestroyMenu(p.menu) +} + +func (p *Win32Menu) UpdateMenuItem(item *MenuItem) { + if item.IsCheckbox() { + for _, itemID := range p.checkboxItems[item] { + var checkState uint = w32.MF_UNCHECKED + if item.checked { + checkState = w32.MF_CHECKED + } + w32.CheckMenuItem(p.menu, uintptr(itemID), checkState) + } + return + } + if item.IsRadio() && item.checked == true { + p.updateRadioGroup(item) + } +} + +func (p *Win32Menu) updateRadioGroups() { + for menuItem := range p.radioGroups { + if menuItem.checked { + p.updateRadioGroup(menuItem) + } + } +} + +func (p *Win32Menu) updateRadioGroup(item *MenuItem) { + for _, radioGroup := range p.radioGroups[item] { + thisMenuID := radioGroup.MenuID(item) + startID, endID := radioGroup.Bounds() + w32.CheckRadio(p.menu, startID, endID, thisMenuID) + + } +} + +func (p *Win32Menu) OnMenuOpen(fn func()) { + p.onMenuOpen = fn +} + +func (p *Win32Menu) OnMenuClose(fn func()) { + p.onMenuClose = fn +} diff --git a/v3/pkg/application/roles.go b/v3/pkg/application/roles.go new file mode 100644 index 000000000..78bbf2c38 --- /dev/null +++ b/v3/pkg/application/roles.go @@ -0,0 +1,164 @@ +package application + +import "runtime" + +// Heavily inspired by Electron (c) 2013-2020 Github Inc. +// Electron License: https://github.com/electron/electron/blob/master/LICENSE + +// Role is a type to identify menu roles +type Role uint + +// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h` +const ( + NoRole Role = iota + AppMenu Role = iota + EditMenu Role = iota + ViewMenu Role = iota + WindowMenu Role = iota + ServicesMenu Role = iota + HelpMenu Role = iota + + Hide Role = iota + HideOthers Role = iota + ShowAll Role = iota + BringAllToFront Role = iota + UnHide Role = iota + About Role = iota + Undo Role = iota + Redo Role = iota + Cut Role = iota + Copy Role = iota + Paste Role = iota + PasteAndMatchStyle Role = iota + SelectAll Role = iota + Delete Role = iota + SpeechMenu Role = iota + Quit Role = iota + FileMenu Role = iota + CloseWindow Role = iota + Reload Role = iota + ForceReload Role = iota + OpenDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + + Minimise Role = iota + Zoom Role = iota + FullScreen Role = iota + + NewFile Role = iota + Open Role = iota + Save Role = iota + SaveAs Role = iota + StartSpeaking Role = iota + StopSpeaking Role = iota + Revert Role = iota + Print Role = iota + PageLayout Role = iota + Find Role = iota + FindAndReplace Role = iota + FindNext Role = iota + FindPrevious Role = iota + Front Role = iota + Help Role = iota +) + +func NewFileMenu() *MenuItem { + fileMenu := NewMenu() + if runtime.GOOS == "darwin" { + fileMenu.AddRole(CloseWindow) + } else { + fileMenu.AddRole(Quit) + } + subMenu := NewSubMenuItem("File") + subMenu.submenu = fileMenu + return subMenu +} + +func NewViewMenu() *MenuItem { + viewMenu := NewMenu() + viewMenu.AddRole(Reload) + viewMenu.AddRole(ForceReload) + addDevToolMenuItem(viewMenu) + viewMenu.AddSeparator() + viewMenu.AddRole(ResetZoom) + viewMenu.AddRole(ZoomIn) + viewMenu.AddRole(ZoomOut) + viewMenu.AddSeparator() + viewMenu.AddRole(ToggleFullscreen) + subMenu := NewSubMenuItem("View") + subMenu.submenu = viewMenu + return subMenu +} + +func NewAppMenu() *MenuItem { + if runtime.GOOS != "darwin" { + return nil + } + appMenu := NewMenu() + appMenu.AddRole(About) + appMenu.AddSeparator() + appMenu.AddRole(ServicesMenu) + appMenu.AddSeparator() + appMenu.AddRole(Hide) + appMenu.AddRole(HideOthers) + appMenu.AddRole(UnHide) + appMenu.AddSeparator() + appMenu.AddRole(Quit) + subMenu := NewSubMenuItem(globalApplication.options.Name) + subMenu.submenu = appMenu + return subMenu +} + +func NewEditMenu() *MenuItem { + editMenu := NewMenu() + editMenu.AddRole(Undo) + editMenu.AddRole(Redo) + editMenu.AddSeparator() + editMenu.AddRole(Cut) + editMenu.AddRole(Copy) + editMenu.AddRole(Paste) + if runtime.GOOS == "darwin" { + editMenu.AddRole(PasteAndMatchStyle) + editMenu.AddRole(Delete) + editMenu.AddRole(SelectAll) + editMenu.AddSeparator() + editMenu.AddRole(SpeechMenu) + } else { + editMenu.AddRole(Delete) + editMenu.AddSeparator() + editMenu.AddRole(SelectAll) + } + subMenu := NewSubMenuItem("Edit") + subMenu.submenu = editMenu + return subMenu +} + +func NewWindowMenu() *MenuItem { + menu := NewMenu() + menu.AddRole(Minimise) + menu.AddRole(Zoom) + if runtime.GOOS == "darwin" { + menu.AddSeparator() + menu.AddRole(Front) + //menu.AddSeparator() + //menu.AddRole(Window) + } else { + menu.AddRole(CloseWindow) + } + subMenu := NewSubMenuItem("Window") + subMenu.submenu = menu + return subMenu +} + +func NewHelpMenu() *MenuItem { + menu := NewMenu() + menu.Add("Learn More").OnClick(func(ctx *Context) { + globalApplication.Window.Current().SetURL("https://wails.io") + }) + subMenu := NewSubMenuItem("Help") + subMenu.submenu = menu + return subMenu +} diff --git a/v3/pkg/application/roles_dev.go b/v3/pkg/application/roles_dev.go new file mode 100644 index 000000000..1c03e398d --- /dev/null +++ b/v3/pkg/application/roles_dev.go @@ -0,0 +1,7 @@ +//go:build !production || devtools + +package application + +func addDevToolMenuItem(viewMenu *Menu) { + viewMenu.AddRole(OpenDevTools) +} diff --git a/v3/pkg/application/roles_production.go b/v3/pkg/application/roles_production.go new file mode 100644 index 000000000..d1af8dba7 --- /dev/null +++ b/v3/pkg/application/roles_production.go @@ -0,0 +1,5 @@ +//go:build production && !devtools + +package application + +func addDevToolMenuItem(viewMenu *Menu) {} diff --git a/v3/pkg/application/screen_darwin.go b/v3/pkg/application/screen_darwin.go new file mode 100644 index 000000000..ad5285e34 --- /dev/null +++ b/v3/pkg/application/screen_darwin.go @@ -0,0 +1,194 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit +#import +#import +#import +#import +#include + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scaleFactor; + double rotation; + bool isPrimary; +} Screen; + + +int GetNumScreens(){ + return [[NSScreen screens] count]; +} + +Screen processScreen(NSScreen* screen){ + Screen returnScreen; + returnScreen.scaleFactor = screen.backingScaleFactor; + + // screen bounds + returnScreen.height = screen.frame.size.height; + returnScreen.width = screen.frame.size.width; + returnScreen.x = screen.frame.origin.x; + returnScreen.y = screen.frame.origin.y; + + // work area + NSRect workArea = [screen visibleFrame]; + returnScreen.w_height = workArea.size.height; + returnScreen.w_width = workArea.size.width; + returnScreen.w_x = workArea.origin.x; + returnScreen.w_y = workArea.origin.y; + + + // adapted from https://stackoverflow.com/a/1237490/4188138 + NSDictionary* screenDictionary = [screen deviceDescription]; + NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; + CGDirectDisplayID displayID = [screenID unsignedIntValue]; + returnScreen.id = [[NSString stringWithFormat:@"%d", displayID] UTF8String]; + + // Get physical monitor size + NSValue *sizeValue = [screenDictionary objectForKey:@"NSDeviceSize"]; + NSSize physicalSize = sizeValue.sizeValue; + returnScreen.p_height = physicalSize.height; + returnScreen.p_width = physicalSize.width; + + // Get the rotation + double rotation = CGDisplayRotation(displayID); + returnScreen.rotation = rotation; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if( @available(macOS 10.15, *) ){ + returnScreen.name = [screen.localizedName UTF8String]; + } +#endif + return returnScreen; +} + +// Get primary screen +Screen GetPrimaryScreen(){ + // Get primary screen + NSScreen *mainScreen = [NSScreen mainScreen]; + return processScreen(mainScreen); +} + +Screen* getAllScreens() { + NSArray *screens = [NSScreen screens]; + Screen* returnScreens = malloc(sizeof(Screen) * screens.count); + for (int i = 0; i < screens.count; i++) { + NSScreen* screen = [screens objectAtIndex:i]; + returnScreens[i] = processScreen(screen); + } + return returnScreens; +} + +Screen getScreenForWindow(void* window){ + NSScreen* screen = ((NSWindow*)window).screen; + return processScreen(screen); +} + +// Get the screen for the system tray +Screen getScreenForSystemTray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSRect frame = statusItem.button.frame; + NSArray *screens = NSScreen.screens; + NSScreen *associatedScreen = nil; + + for (NSScreen *screen in screens) { + if (NSPointInRect(frame.origin, screen.frame)) { + associatedScreen = screen; + break; + } + } + return processScreen(associatedScreen); +} + +void* getWindowForSystray(void* nsStatusItem) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + return statusItem.button.window; +} + + +*/ +import "C" +import "unsafe" + +func cScreenToScreen(screen C.Screen) *Screen { + + return &Screen{ + Size: Size{ + Width: int(screen.p_width), + Height: int(screen.p_height), + }, + Bounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + PhysicalBounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + WorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + PhysicalWorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + ScaleFactor: float32(screen.scaleFactor), + ID: C.GoString(screen.id), + Name: C.GoString(screen.name), + IsPrimary: bool(screen.isPrimary), + Rotation: float32(screen.rotation), + } +} + +func (m *macosApp) getPrimaryScreen() (*Screen, error) { + cScreen := C.GetPrimaryScreen() + return cScreenToScreen(cScreen), nil +} + +func (m *macosApp) getScreens() ([]*Screen, error) { + cScreens := C.getAllScreens() + defer C.free(unsafe.Pointer(cScreens)) + numScreens := int(C.GetNumScreens()) + displays := make([]*Screen, numScreens) + cScreenHeaders := (*[1 << 30]C.Screen)(unsafe.Pointer(cScreens))[:numScreens:numScreens] + for i := 0; i < numScreens; i++ { + displays[i] = cScreenToScreen(cScreenHeaders[i]) + } + return displays, nil +} + +func getScreenForWindow(window *macosWebviewWindow) (*Screen, error) { + cScreen := C.getScreenForWindow(window.nsWindow) + return cScreenToScreen(cScreen), nil +} + +func getScreenForSystray(systray *macosSystemTray) (*Screen, error) { + // Get the Window for the status item + // https://stackoverflow.com/a/5875019/4188138 + window := C.getWindowForSystray(systray.nsStatusItem) + cScreen := C.getScreenForWindow(window) + return cScreenToScreen(cScreen), nil +} diff --git a/v3/pkg/application/screen_linux.go b/v3/pkg/application/screen_linux.go new file mode 100644 index 000000000..f6dfdf59d --- /dev/null +++ b/v3/pkg/application/screen_linux.go @@ -0,0 +1,37 @@ +//go:build linux + +package application + +import ( + "sync" +) + +func (a *linuxApp) getPrimaryScreen() (*Screen, error) { + var wg sync.WaitGroup + var screen *Screen + var err error + wg.Add(1) + InvokeSync(func() { + screen, err = getPrimaryScreen() + wg.Done() + }) + wg.Wait() + return screen, err +} + +func (a *linuxApp) getScreens() ([]*Screen, error) { + var wg sync.WaitGroup + var screens []*Screen + var err error + wg.Add(1) + InvokeSync(func() { + screens, err = getScreens(a.application) + wg.Done() + }) + wg.Wait() + return screens, err +} + +func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { + return window.getScreen() +} diff --git a/v3/pkg/application/screen_windows.go b/v3/pkg/application/screen_windows.go new file mode 100644 index 000000000..b6328f809 --- /dev/null +++ b/v3/pkg/application/screen_windows.go @@ -0,0 +1,88 @@ +//go:build windows + +package application + +import ( + "errors" + "strconv" + + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +func (m *windowsApp) processAndCacheScreens() error { + allScreens, err := w32.GetAllScreens() + if err != nil { + return err + } + + // Convert result to []*Screen + var screens []*Screen + + for _, screen := range allScreens { + x := int(screen.MONITORINFOEX.RcMonitor.Left) + y := int(screen.MONITORINFOEX.RcMonitor.Top) + right := int(screen.MONITORINFOEX.RcMonitor.Right) + bottom := int(screen.MONITORINFOEX.RcMonitor.Bottom) + width := right - x + height := bottom - y + + workArea := Rect{ + X: int(screen.MONITORINFOEX.RcWork.Left), + Y: int(screen.MONITORINFOEX.RcWork.Top), + Width: int(screen.MONITORINFOEX.RcWork.Right - screen.MONITORINFOEX.RcWork.Left), + Height: int(screen.MONITORINFOEX.RcWork.Bottom - screen.MONITORINFOEX.RcWork.Top), + } + + screens = append(screens, &Screen{ + ID: hMonitorToScreenID(screen.HMonitor), + Name: windows.UTF16ToString(screen.MONITORINFOEX.SzDevice[:]), + X: x, + Y: y, + Size: Size{Width: width, Height: height}, + Bounds: Rect{X: x, Y: y, Width: width, Height: height}, + PhysicalBounds: Rect{X: x, Y: y, Width: width, Height: height}, + WorkArea: workArea, + PhysicalWorkArea: workArea, + IsPrimary: screen.IsPrimary, + ScaleFactor: screen.ScaleFactor, + Rotation: 0, + }) + } + + err = m.parent.Screen.LayoutScreens(screens) + if err != nil { + return err + } + + return nil +} + +// NOTE: should be moved to *App after DPI is implemented in all platforms +func (m *windowsApp) getScreens() ([]*Screen, error) { + return m.parent.Screen.screens, nil +} + +// NOTE: should be moved to *App after DPI is implemented in all platforms +func (m *windowsApp) getPrimaryScreen() (*Screen, error) { + return m.parent.Screen.primaryScreen, nil +} + +func getScreenForWindow(window *windowsWebviewWindow) (*Screen, error) { + return ScreenNearestPhysicalRect(window.physicalBounds()), nil +} + +func getScreenForWindowHwnd(hwnd w32.HWND) (*Screen, error) { + hMonitor := w32.MonitorFromWindow(hwnd, w32.MONITOR_DEFAULTTONEAREST) + screenID := hMonitorToScreenID(hMonitor) + for _, screen := range globalApplication.Screen.screens { + if screen.ID == screenID { + return screen, nil + } + } + return nil, errors.New("screen not found for window") +} + +func hMonitorToScreenID(hMonitor uintptr) string { + return strconv.Itoa(int(hMonitor)) +} diff --git a/v3/pkg/application/screenmanager.go b/v3/pkg/application/screenmanager.go new file mode 100644 index 000000000..6caa1cd33 --- /dev/null +++ b/v3/pkg/application/screenmanager.go @@ -0,0 +1,875 @@ +package application + +import ( + "errors" + "math" + "sort" +) + +// Heavily inspired by the Chromium project (Copyright 2015 The Chromium Authors) +// Chromium License: https://chromium.googlesource.com/chromium/src/+/HEAD/LICENSE + +type ScreenManager struct { + app *App + screens []*Screen + primaryScreen *Screen +} + +// newScreenManager creates a new ScreenManager instance +func newScreenManager(app *App) *ScreenManager { + return &ScreenManager{ + app: app, + } +} + +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + ScaleFactor float32 // The scale factor of the display (DPI/96) + X int // The x-coordinate of the top-left corner of the rectangle + Y int // The y-coordinate of the top-left corner of the rectangle + Size Size // The size of the display + Bounds Rect // The bounds of the display + PhysicalBounds Rect // The physical bounds of the display (before scaling) + WorkArea Rect // The work area of the display + PhysicalWorkArea Rect // The physical work area of the display (before scaling) + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Point struct { + X int + Y int +} +type Size struct { + Width int + Height int +} + +type Alignment int +type OffsetReference int + +const ( + TOP Alignment = iota + RIGHT + BOTTOM + LEFT +) + +const ( + BEGIN OffsetReference = iota // TOP or LEFT + END // BOTTOM or RIGHT +) + +// ScreenPlacement specifies where the screen (S) is placed relative to +// parent (P) screen. In the following example, (S) is RIGHT aligned to (P) +// with a positive offset and a BEGIN (top) offset reference. +// +// . +------------+ + +// . | | | offset +// . | P | v +// . | +--------+ +// . | | | +// . +------------+ S | +// . | | +// . +--------+ +type ScreenPlacement struct { + screen *Screen + parent *Screen + alignment Alignment + offset int + offsetReference OffsetReference +} + +func (r Rect) Origin() Point { + return Point{ + X: r.X, + Y: r.Y, + } +} + +func (s Screen) Origin() Point { + return Point{ + X: s.X, + Y: s.Y, + } +} + +func (r Rect) Corner() Point { + return Point{ + X: r.right(), + Y: r.bottom(), + } +} + +func (r Rect) InsideCorner() Point { + return Point{ + X: r.right() - 1, + Y: r.bottom() - 1, + } +} + +func (r Rect) right() int { + return r.X + r.Width +} + +func (r Rect) bottom() int { + return r.Y + r.Height +} + +func (s Screen) right() int { + return s.Bounds.right() +} + +func (s Screen) bottom() int { + return s.Bounds.bottom() +} + +func (s Screen) scale(value int, toDip bool) int { + // Round up when scaling down and round down when scaling up. + // This mix rounding strategy prevents drift over time when applying multiple scaling back and forth. + // In addition, It has been shown that using this approach minimized rounding issues and improved overall + // precision when converting between DIP and physical coordinates. + if toDip { + return int(math.Ceil(float64(value) / float64(s.ScaleFactor))) + } else { + return int(math.Floor(float64(value) * float64(s.ScaleFactor))) + } +} + +func (r Rect) Size() Size { + return Size{ + Width: r.Width, + Height: r.Height, + } +} + +func (r Rect) IsEmpty() bool { + return r.Width <= 0 || r.Height <= 0 +} + +func (r Rect) Contains(pt Point) bool { + return pt.X >= r.X && pt.X < r.X+r.Width && pt.Y >= r.Y && pt.Y < r.Y+r.Height +} + +// Get intersection with another rect +func (r Rect) Intersect(otherRect Rect) Rect { + if r.IsEmpty() || otherRect.IsEmpty() { + return Rect{} + } + + maxLeft := max(r.X, otherRect.X) + maxTop := max(r.Y, otherRect.Y) + minRight := min(r.right(), otherRect.right()) + minBottom := min(r.bottom(), otherRect.bottom()) + + if minRight > maxLeft && minBottom > maxTop { + return Rect{ + X: maxLeft, + Y: maxTop, + Width: minRight - maxLeft, + Height: minBottom - maxTop, + } + } + return Rect{} +} + +// Check if screens intersects another screen +func (s *Screen) intersects(otherScreen *Screen) bool { + maxLeft := max(s.X, otherScreen.X) + maxTop := max(s.Y, otherScreen.Y) + minRight := min(s.right(), otherScreen.right()) + minBottom := min(s.bottom(), otherScreen.bottom()) + + return minRight > maxLeft && minBottom > maxTop +} + +// Get distance from another rect (squared) +func (r Rect) distanceFromRectSquared(otherRect Rect) int { + // If they intersect, return negative area of intersection + intersection := r.Intersect(otherRect) + if !intersection.IsEmpty() { + return -(intersection.Width * intersection.Height) + } + + dX := max(0, max(r.X-otherRect.right(), otherRect.X-r.right())) + dY := max(0, max(r.Y-otherRect.bottom(), otherRect.Y-r.bottom())) + + // Distance squared + return dX*dX + dY*dY +} + +// Apply screen placement +func (p ScreenPlacement) apply() { + parentBounds := p.parent.Bounds + screenBounds := p.screen.Bounds + + newX := parentBounds.X + newY := parentBounds.Y + offset := p.offset + + if p.alignment == TOP || p.alignment == BOTTOM { + if p.offsetReference == END { + offset = parentBounds.Width - offset - screenBounds.Width + } + offset = min(offset, parentBounds.Width) + offset = max(offset, -screenBounds.Width) + newX += offset + if p.alignment == TOP { + newY -= screenBounds.Height + } else { + newY += parentBounds.Height + } + } else { + if p.offsetReference == END { + offset = parentBounds.Height - offset - screenBounds.Height + } + offset = min(offset, parentBounds.Height) + offset = max(offset, -screenBounds.Height) + newY += offset + if p.alignment == LEFT { + newX -= screenBounds.Width + } else { + newX += parentBounds.Width + } + } + + p.screen.move(newX, newY) +} + +func (s *Screen) absoluteToRelativeDipPoint(dipPoint Point) Point { + return Point{ + X: dipPoint.X - s.Bounds.X, + Y: dipPoint.Y - s.Bounds.Y, + } +} + +func (s *Screen) relativeToAbsoluteDipPoint(dipPoint Point) Point { + return Point{ + X: dipPoint.X + s.Bounds.X, + Y: dipPoint.Y + s.Bounds.Y, + } +} + +func (s *Screen) absoluteToRelativePhysicalPoint(physicalPoint Point) Point { + return Point{ + X: physicalPoint.X - s.PhysicalBounds.X, + Y: physicalPoint.Y - s.PhysicalBounds.Y, + } +} + +func (s *Screen) relativeToAbsolutePhysicalPoint(physicalPoint Point) Point { + return Point{ + X: physicalPoint.X + s.PhysicalBounds.X, + Y: physicalPoint.Y + s.PhysicalBounds.Y, + } +} + +func (s *Screen) move(newX, newY int) { + workAreaOffsetX := s.WorkArea.X - s.X + workAreaOffsetY := s.WorkArea.Y - s.Y + + s.X = newX + s.Y = newY + s.Bounds.X = newX + s.Bounds.Y = newY + s.WorkArea.X = newX + workAreaOffsetX + s.WorkArea.Y = newY + workAreaOffsetY +} + +func (s *Screen) applyDPIScaling() { + if s.ScaleFactor == 1 { + return + } + workAreaOffsetX := s.WorkArea.X - s.Bounds.X + workAreaOffsetY := s.WorkArea.Y - s.Bounds.Y + + s.WorkArea.X = s.Bounds.X + s.scale(workAreaOffsetX, true) + s.WorkArea.Y = s.Bounds.Y + s.scale(workAreaOffsetY, true) + + s.Bounds.Width = s.scale(s.PhysicalBounds.Width, true) + s.Bounds.Height = s.scale(s.PhysicalBounds.Height, true) + s.WorkArea.Width = s.scale(s.PhysicalWorkArea.Width, true) + s.WorkArea.Height = s.scale(s.PhysicalWorkArea.Height, true) + + s.Size.Width = s.Bounds.Width + s.Size.Height = s.Bounds.Height +} + +func (s *Screen) dipToPhysicalPoint(dipPoint Point, isCorner bool) Point { + relativePoint := s.absoluteToRelativeDipPoint(dipPoint) + scaledRelativePoint := Point{ + X: s.scale(relativePoint.X, false), + Y: s.scale(relativePoint.Y, false), + } + // Align edge points (fixes rounding issues) + edgeOffset := 1 + if isCorner { + edgeOffset = 0 + } + if relativePoint.X == s.Bounds.Width-edgeOffset { + scaledRelativePoint.X = s.PhysicalBounds.Width - edgeOffset + } + if relativePoint.Y == s.Bounds.Height-edgeOffset { + scaledRelativePoint.Y = s.PhysicalBounds.Height - edgeOffset + } + return s.relativeToAbsolutePhysicalPoint(scaledRelativePoint) +} + +func (s *Screen) physicalToDipPoint(physicalPoint Point, isCorner bool) Point { + relativePoint := s.absoluteToRelativePhysicalPoint(physicalPoint) + scaledRelativePoint := Point{ + X: s.scale(relativePoint.X, true), + Y: s.scale(relativePoint.Y, true), + } + // Align edge points (fixes rounding issues) + edgeOffset := 1 + if isCorner { + edgeOffset = 0 + } + if relativePoint.X == s.PhysicalBounds.Width-edgeOffset { + scaledRelativePoint.X = s.Bounds.Width - edgeOffset + } + if relativePoint.Y == s.PhysicalBounds.Height-edgeOffset { + scaledRelativePoint.Y = s.Bounds.Height - edgeOffset + } + return s.relativeToAbsoluteDipPoint(scaledRelativePoint) +} + +func (s *Screen) dipToPhysicalRect(dipRect Rect) Rect { + origin := s.dipToPhysicalPoint(dipRect.Origin(), false) + corner := s.dipToPhysicalPoint(dipRect.Corner(), true) + + return Rect{ + X: origin.X, + Y: origin.Y, + Width: corner.X - origin.X, + Height: corner.Y - origin.Y, + } +} + +func (s *Screen) physicalToDipRect(physicalRect Rect) Rect { + origin := s.physicalToDipPoint(physicalRect.Origin(), false) + corner := s.physicalToDipPoint(physicalRect.Corner(), true) + + return Rect{ + X: origin.X, + Y: origin.Y, + Width: corner.X - origin.X, + Height: corner.Y - origin.Y, + } +} + +// Layout screens in the virtual space with DIP calculations and cache the screens +// for future coordinate transformation between the physical and logical (DIP) space +func (m *ScreenManager) LayoutScreens(screens []*Screen) error { + if screens == nil || len(screens) == 0 { + return errors.New("screens parameter is nil or empty") + } + m.screens = screens + + err := m.calculateScreensDipCoordinates() + if err != nil { + return err + } + + return nil +} + +func (m *ScreenManager) GetAll() []*Screen { + return m.screens +} + +func (m *ScreenManager) GetPrimary() *Screen { + return m.primaryScreen +} + +// Reference: https://source.chromium.org/chromium/chromium/src/+/main:ui/display/win/screen_win.cc;l=317 +func (m *ScreenManager) calculateScreensDipCoordinates() error { + remainingScreens := []*Screen{} + + // Find the primary screen + m.primaryScreen = nil + for _, screen := range m.screens { + if screen.IsPrimary { + m.primaryScreen = screen + } else { + remainingScreens = append(remainingScreens, screen) + } + } + if m.primaryScreen == nil { + return errors.New("no primary screen found") + } else if len(remainingScreens) != len(m.screens)-1 { + return errors.New("invalid primary screen found") + } + + // Build screens tree using the primary screen as root + screensPlacements := []ScreenPlacement{} + availableParents := []*Screen{m.primaryScreen} + for len(availableParents) > 0 { + // Pop a parent + end := len(availableParents) - 1 + parent := availableParents[end] + availableParents = availableParents[:end] + // Find touching screens + for _, child := range m.findAndRemoveTouchingScreens(parent, &remainingScreens) { + screenPlacement := m.calculateScreenPlacement(child, parent) + screensPlacements = append(screensPlacements, screenPlacement) + availableParents = append(availableParents, child) + } + } + + // Apply screens DPI scaling and placement starting with + // the primary screen and then dependent screens + m.primaryScreen.applyDPIScaling() + for _, placement := range screensPlacements { + placement.screen.applyDPIScaling() + placement.apply() + } + + // Now that all the placements have been applied, + // we must detect and fix any overlapping screens. + m.deIntersectScreens(screensPlacements) + + return nil +} + +// Returns a ScreenPlacement for |screen| relative to |parent|. +// Note that ScreenPlacement's are always in DIPs, so this also performs the +// required scaling. +// References: +// - https://github.com/chromium/chromium/blob/main/ui/display/win/scaling_util.h#L25 +// - https://github.com/chromium/chromium/blob/main/ui/display/win/scaling_util.cc#L142 +func (m *ScreenManager) calculateScreenPlacement(screen, parent *Screen) ScreenPlacement { + // Examples (The offset is indicated by the arrow.): + // Scaled and Unscaled Coordinates + // +--------------+ + Since both screens are of the same scale + // | | | factor, relative positions remain the same. + // | Parent | V + // | 1x +----------+ + // | | | + // +--------------+ Screen | + // | 1x | + // +----------+ + // + // Unscaled Coordinates + // +--------------+ The 2x screen is offset to maintain a + // | | similar neighboring relationship with the 1x + // | Parent | parent. Screen's position is based off of the + // | 1x +----------+ percentage position along its parent. This + // | | | percentage position is preserved in the scaled + // +--------------+ Screen | coordinates. + // | 2x | + // +----------+ + // Scaled Coordinates + // +--------------+ + + // | | | + // | Parent | V + // | 1x +-----+ + // | | S 2x| + // +--------------+-----+ + // + // + // Unscaled Coordinates + // +--------------+ The parent screen has a 2x scale factor. + // | | The offset is adjusted to maintain the + // | | relative positioning of the 1x screen in + // | Parent +----------+ the scaled coordinate space. Screen's + // | 2x | | position is based off of the percentage + // | | Screen | position along its parent. This percentage + // | | 1x | position is preserved in the scaled + // +--------------+ | coordinates. + // | | + // +----------+ + // Scaled Coordinates + // +-------+ + + // | | V + // | Parent+----------+ + // | 2x | | + // +-------+ Screen | + // | 1x | + // | | + // | | + // +----------+ + // + // Unscaled Coordinates + // +----------+ In this case, parent lies between the top and + // | | bottom of parent. The roles are reversed when + // +-------+ | this occurs, and screen is placed to maintain + // | | Screen | parent's relative position along screen. + // | Parent| 1x | + // | 2x | | + // +-------+ | + // +----------+ + // Scaled Coordinates + // ^ +----------+ + // | | | + // + +----+ | + // |Prnt| Screen | + // | 2x | 1x | + // +----+ | + // | | + // +----------+ + // + // Scaled and Unscaled Coordinates + // +--------+ If the two screens are bottom aligned or + // | | right aligned, the ScreenPlacement will + // | +--------+ have an offset of 0 relative to the + // | | | end of the screen. + // | | | + // +--------+--------+ + + placement := ScreenPlacement{ + screen: screen, + parent: parent, + alignment: m.getScreenAlignment(screen, parent), + offset: 0, + offsetReference: BEGIN, + } + + screenBegin, screenEnd := 0, 0 + parentBegin, parentEnd := 0, 0 + + switch placement.alignment { + case TOP, BOTTOM: + screenBegin = screen.X + screenEnd = screen.right() + parentBegin = parent.X + parentEnd = parent.right() + case LEFT, RIGHT: + screenBegin = screen.Y + screenEnd = screen.bottom() + parentBegin = parent.Y + parentEnd = parent.bottom() + } + + // Since we're calculating offsets, make everything relative to parentBegin + parentEnd -= parentBegin + screenBegin -= parentBegin + screenEnd -= parentBegin + parentBegin = 0 + + // There are a few ways lines can intersect: + // End Aligned + // SCREEN's offset is relative to the END (BOTTOM or RIGHT). + // +-PARENT----------------+ + // +-SCREEN-------------+ + // + // Positioning based off of |screenBegin|. + // SCREEN's offset is simply a percentage of its position on PARENT. + // +-PARENT----------------+ + // ^+-SCREEN------------+ + // + // Positioning based off of |screenEnd|. + // SCREEN's offset is dependent on the percentage of its end position on PARENT. + // +-PARENT----------------+ + // +-SCREEN------------+^ + // + // Positioning based off of |parentBegin| on SCREEN. + // SCREEN's offset is dependent on the percentage of its position on PARENT. + // +-PARENT----------------+ + // ^+-SCREEN--------------------------+ + + if screenEnd == parentEnd { + placement.offsetReference = END + placement.offset = 0 + } else if screenBegin >= parentBegin { + placement.offsetReference = BEGIN + placement.offset = m.scaleOffset(parentEnd, parent.ScaleFactor, screenBegin) + } else if screenEnd <= parentEnd { + placement.offsetReference = END + placement.offset = m.scaleOffset(parentEnd, parent.ScaleFactor, parentEnd-screenEnd) + } else { + placement.offsetReference = BEGIN + placement.offset = m.scaleOffset(screenEnd-screenBegin, screen.ScaleFactor, screenBegin) + } + + return placement +} + +// Get screen alignment relative to parent (TOP, RIGHT, BOTTOM, LEFT) +func (m *ScreenManager) getScreenAlignment(screen, parent *Screen) Alignment { + maxLeft := max(screen.X, parent.X) + maxTop := max(screen.Y, parent.Y) + minRight := min(screen.right(), parent.right()) + minBottom := min(screen.bottom(), parent.bottom()) + + // Corners touching + if maxLeft == minRight && maxTop == minBottom { + if screen.Y == maxTop { + return BOTTOM + } else if parent.X == maxLeft { + return LEFT + } + return TOP + } + + // Vertical edge touching + if maxLeft == minRight { + if screen.X == maxLeft { + return RIGHT + } else { + return LEFT + } + } + + // Horizontal edge touching + if maxTop == minBottom { + if screen.Y == maxTop { + return BOTTOM + } else { + return TOP + } + } + + return -1 // Shouldn't be reached +} + +func (m *ScreenManager) deIntersectScreens(screensPlacements []ScreenPlacement) { + parentIDMap := make(map[string]string) + for _, placement := range screensPlacements { + parentIDMap[placement.screen.ID] = placement.parent.ID + } + + treeDepthMap := make(map[string]int) + for _, screen := range m.screens { + id, ok, depth := screen.ID, true, 0 + const maxDepth = 100 + for id != m.primaryScreen.ID && depth < maxDepth { + depth++ + id, ok = parentIDMap[id] + if !ok { + depth = maxDepth + } + } + treeDepthMap[screen.ID] = depth + } + + sortedScreens := make([]*Screen, len(m.screens)) + copy(sortedScreens, m.screens) + + // Sort the screens first by their depth in the screen hierarchy tree, + // and then by distance from screen origin to primary origin. This way we + // process the screens starting at the root (the primary screen), in the + // order of their descendance spanning out from the primary screen. + sort.Slice(sortedScreens, func(i, j int) bool { + s1, s2 := m.screens[i], m.screens[j] + s1_depth := treeDepthMap[s1.ID] + s2_depth := treeDepthMap[s2.ID] + + if s1_depth != s2_depth { + return s1_depth < s2_depth + } + + // Distance squared + s1_distance := s1.X*s1.X + s1.Y*s1.Y + s2_distance := s2.X*s2.X + s2.Y*s2.Y + if s1_distance != s2_distance { + return s1_distance < s2_distance + } + + return s1.ID < s2.ID + }) + + for i := 1; i < len(sortedScreens); i++ { + targetScreen := sortedScreens[i] + for j := 0; j < i; j++ { + sourceScreen := sortedScreens[j] + if targetScreen.intersects(sourceScreen) { + m.fixScreenIntersection(targetScreen, sourceScreen) + } + } + } +} + +// Offset the target screen along either X or Y axis away from the origin +// so that it removes the intersection with the source screen +// This function assume both screens already intersect. +func (m *ScreenManager) fixScreenIntersection(targetScreen, sourceScreen *Screen) { + offsetX, offsetY := 0, 0 + + if targetScreen.X >= 0 { + offsetX = sourceScreen.right() - targetScreen.X + } else { + offsetX = -(targetScreen.right() - sourceScreen.X) + } + + if targetScreen.Y >= 0 { + offsetY = sourceScreen.bottom() - targetScreen.Y + } else { + offsetY = -(targetScreen.bottom() - sourceScreen.Y) + } + + // Choose the smaller offset (X or Y) + if math.Abs(float64(offsetX)) <= math.Abs(float64(offsetY)) { + offsetY = 0 + } else { + offsetX = 0 + } + + // Apply the offset + newX := targetScreen.X + offsetX + newY := targetScreen.Y + offsetY + targetScreen.move(newX, newY) +} + +func (m *ScreenManager) findAndRemoveTouchingScreens(parent *Screen, screens *[]*Screen) []*Screen { + touchingScreens := []*Screen{} + remainingScreens := []*Screen{} + + for _, screen := range *screens { + if m.areScreensTouching(parent, screen) { + touchingScreens = append(touchingScreens, screen) + } else { + remainingScreens = append(remainingScreens, screen) + } + } + *screens = remainingScreens + return touchingScreens +} + +func (m *ScreenManager) areScreensTouching(a, b *Screen) bool { + maxLeft := max(a.X, b.X) + maxTop := max(a.Y, b.Y) + minRight := min(a.right(), b.right()) + minBottom := min(a.bottom(), b.bottom()) + return (maxLeft == minRight && maxTop <= minBottom) || (maxTop == minBottom && maxLeft <= minRight) +} + +// Scale |unscaledOffset| to the same relative position on |unscaledLength| +// based off of |unscaledLength|'s |scaleFactor| +func (m *ScreenManager) scaleOffset(unscaledLength int, scaleFactor float32, unscaledOffset int) int { + scaledLength := float32(unscaledLength) / scaleFactor + percent := float32(unscaledOffset) / float32(unscaledLength) + return int(math.Floor(float64(scaledLength * percent))) +} + +func (m *ScreenManager) screenNearestPoint(point Point, isPhysical bool) *Screen { + for _, screen := range m.screens { + if isPhysical { + if screen.PhysicalBounds.Contains(point) { + return screen + } + } else { + if screen.Bounds.Contains(point) { + return screen + } + } + } + return m.primaryScreen +} + +func (m *ScreenManager) screenNearestRect(rect Rect, isPhysical bool, excludedScreens map[string]bool) *Screen { + var nearestScreen *Screen + var distance, nearestScreenDistance int + for _, screen := range m.screens { + if excludedScreens[screen.ID] { + continue + } + if isPhysical { + distance = rect.distanceFromRectSquared(screen.PhysicalBounds) + } else { + distance = rect.distanceFromRectSquared(screen.Bounds) + } + if nearestScreen == nil || distance < nearestScreenDistance { + nearestScreen = screen + nearestScreenDistance = distance + } + } + if !isPhysical && len(excludedScreens) < len(m.screens)-1 { + // Make sure to give the same screen that would be given by the physical rect + // of this dip rect so transforming back and forth always gives the same result. + // This is important because it could happen that a dip rect intersects Screen1 + // more than Screen2 but in the physical layout Screen2 will scale up or Screen1 + // will scale down causing the intersection area to change so transforming back + // would give a different rect. + physicalRect := nearestScreen.dipToPhysicalRect(rect) + physicalRectScreen := m.screenNearestRect(physicalRect, true, nil) + if nearestScreen != physicalRectScreen { + if excludedScreens == nil { + excludedScreens = make(map[string]bool) + } + excludedScreens[nearestScreen.ID] = true + return m.screenNearestRect(rect, isPhysical, excludedScreens) + } + } + return nearestScreen +} + +func (m *ScreenManager) DipToPhysicalPoint(dipPoint Point) Point { + screen := m.ScreenNearestDipPoint(dipPoint) + return screen.dipToPhysicalPoint(dipPoint, false) +} + +func (m *ScreenManager) PhysicalToDipPoint(physicalPoint Point) Point { + screen := m.ScreenNearestPhysicalPoint(physicalPoint) + return screen.physicalToDipPoint(physicalPoint, false) +} + +func (m *ScreenManager) DipToPhysicalRect(dipRect Rect) Rect { + screen := m.ScreenNearestDipRect(dipRect) + return screen.dipToPhysicalRect(dipRect) +} + +func (m *ScreenManager) PhysicalToDipRect(physicalRect Rect) Rect { + screen := m.ScreenNearestPhysicalRect(physicalRect) + return screen.physicalToDipRect(physicalRect) +} + +func (m *ScreenManager) ScreenNearestPhysicalPoint(physicalPoint Point) *Screen { + return m.screenNearestPoint(physicalPoint, true) +} + +func (m *ScreenManager) ScreenNearestDipPoint(dipPoint Point) *Screen { + return m.screenNearestPoint(dipPoint, false) +} + +func (m *ScreenManager) ScreenNearestPhysicalRect(physicalRect Rect) *Screen { + return m.screenNearestRect(physicalRect, true, nil) +} + +func (m *ScreenManager) ScreenNearestDipRect(dipRect Rect) *Screen { + return m.screenNearestRect(dipRect, false, nil) +} + +// ================================================================================================ +// Exported application-level methods for internal convenience and availability to application devs + +func DipToPhysicalPoint(dipPoint Point) Point { + return globalApplication.Screen.DipToPhysicalPoint(dipPoint) +} + +func PhysicalToDipPoint(physicalPoint Point) Point { + return globalApplication.Screen.PhysicalToDipPoint(physicalPoint) +} + +func DipToPhysicalRect(dipRect Rect) Rect { + return globalApplication.Screen.DipToPhysicalRect(dipRect) +} + +func PhysicalToDipRect(physicalRect Rect) Rect { + return globalApplication.Screen.PhysicalToDipRect(physicalRect) +} + +func ScreenNearestPhysicalPoint(physicalPoint Point) *Screen { + return globalApplication.Screen.ScreenNearestPhysicalPoint(physicalPoint) +} + +func ScreenNearestDipPoint(dipPoint Point) *Screen { + return globalApplication.Screen.ScreenNearestDipPoint(dipPoint) +} + +func ScreenNearestPhysicalRect(physicalRect Rect) *Screen { + return globalApplication.Screen.ScreenNearestPhysicalRect(physicalRect) +} + +func ScreenNearestDipRect(dipRect Rect) *Screen { + return globalApplication.Screen.ScreenNearestDipRect(dipRect) +} diff --git a/v3/pkg/application/screenmanager_test.go b/v3/pkg/application/screenmanager_test.go new file mode 100644 index 000000000..1e58e3fd1 --- /dev/null +++ b/v3/pkg/application/screenmanager_test.go @@ -0,0 +1,716 @@ +package application_test + +import ( + "fmt" + "math" + "slices" + "strconv" + "testing" + + "github.com/matryer/is" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type ScreenDef struct { + id int + w, h int + s float32 + parent ScreenDefParent + name string +} + +type ScreenDefParent struct { + id int + align string + offset int +} + +type ScreensLayout struct { + name string + screens []ScreenDef +} + +type ParsedLayout struct { + name string + screens []*application.Screen +} + +func exampleLayouts() []ParsedLayout { + layouts := [][]ScreensLayout{ + { + // Normal examples (demonstrate real life scenarios) + { + name: "Single 4k monitor", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + }, + }, + { + name: "Two monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + }, + }, + { + name: "Two monitors (2)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1, name: `23" FHD 96DPI`}, + {id: 2, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }, + }, + { + name: "Three monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }, + }, + { + name: "Four monitors", + screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 0}, name: `23" FHD 96DPI (125%)`}, + {id: 4, w: 1080, h: 1920, s: 1, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD (90deg)`}, + }, + }, + }, + { + // Test cases examples (demonstrate the algorithm basics) + { + name: "Child scaled, Start offset", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}, name: "Child"}, + }, + }, + { + name: "Child scaled, End offset", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: -600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, Start offset percent", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, End offset percent", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: -600}, name: "Child"}, + }, + }, + { + name: "Parent scaled, Start align", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1100, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: "Child"}, + }, + }, + { + name: "Parent scaled, End align", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: "Child"}, + }, + }, + { + name: "Parent scaled, in-between", + screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5, name: "Parent"}, + {id: 2, w: 1200, h: 1500, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: -250}, name: "Child"}, + }, + }, + }, + { + // Edge cases examples + { + name: "Parent order (5 is parent of 4)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 600, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: -200}}, + {id: 3, w: 800, h: 800, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 0}}, + {id: 4, w: 800, h: 1080, s: 1.5, parent: ScreenDefParent{id: 2, align: "re", offset: 100}}, + {id: 5, w: 600, h: 600, s: 1, parent: ScreenDefParent{id: 3, align: "r", offset: 100}}, + }, + }, + { + name: "de-intersection reparent", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1680, h: 1050, s: 1.25, parent: ScreenDefParent{id: 1, align: "r", offset: 10}}, + {id: 3, w: 1440, h: 900, s: 1.5, parent: ScreenDefParent{id: 1, align: "le", offset: 150}}, + {id: 4, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 3, align: "bc", offset: -200}}, + {id: 5, w: 1024, h: 768, s: 1.25, parent: ScreenDefParent{id: 4, align: "r", offset: 400}}, + }, + }, + { + name: "de-intersection (unattached child)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 1, align: "le", offset: 10}}, + {id: 3, w: 1024, h: 768, s: 1.25, parent: ScreenDefParent{id: 2, align: "b", offset: 100}}, + {id: 4, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 3, align: "r", offset: 500}}, + }, + }, + { + name: "Multiple de-intersection", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 1, align: "be", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 4, align: "be", offset: 100}}, + }, + }, + { + name: "Multiple de-intersection (left-side)", + screens: []ScreenDef{ + {id: 1, w: 1920, h: 1080, s: 1}, + {id: 2, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 1, align: "le", offset: 0}}, + {id: 3, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 2, align: "b", offset: 300}}, + {id: 4, w: 1024, h: 768, s: 1.5, parent: ScreenDefParent{id: 2, align: "le", offset: 100}}, + {id: 5, w: 1024, h: 768, s: 1, parent: ScreenDefParent{id: 4, align: "be", offset: 100}}, + }, + }, + { + name: "Parent de-intersection child offset", + screens: []ScreenDef{ + {id: 1, w: 1600, h: 1600, s: 1.5}, + {id: 2, w: 800, h: 800, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + {id: 3, w: 800, h: 800, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 800}}, + {id: 4, w: 800, h: 1600, s: 1, parent: ScreenDefParent{id: 2, align: "r", offset: 0}}, + }, + }, + }, + } + + parsedLayouts := []ParsedLayout{} + + for _, section := range layouts { + for _, layout := range section { + parsedLayouts = append(parsedLayouts, parseLayout(layout)) + } + } + + return parsedLayouts +} + +// Parse screens layout from easy-to-define ScreenDef for testing to actual Screens layout +func parseLayout(layout ScreensLayout) ParsedLayout { + screens := []*application.Screen{} + + for _, screen := range layout.screens { + var x, y int + w := screen.w + h := screen.h + + if screen.parent.id > 0 { + idx := slices.IndexFunc(screens, func(s *application.Screen) bool { return s.ID == strconv.Itoa(screen.parent.id) }) + parent := screens[idx].Bounds + offset := screen.parent.offset + align := screen.parent.align + align2 := "" + + if len(align) == 2 { + align2 = string(align[1]) + align = string(align[0]) + } + + x = parent.X + y = parent.Y + // t: top, b: bottom, l: left, r: right, e: edge, c: corner + if align == "t" || align == "b" { + x += offset + if align2 == "e" || align2 == "c" { + x += parent.Width + } + if align2 == "e" { + x -= w + } + if align == "t" { + y -= h + } else { + y += parent.Height + } + } else { + y += offset + if align2 == "e" || align2 == "c" { + y += parent.Height + } + if align2 == "e" { + y -= h + } + if align == "l" { + x -= w + } else { + x += parent.Width + } + } + } + name := screen.name + if name == "" { + name = "Display" + strconv.Itoa(screen.id) + } + screens = append(screens, &application.Screen{ + ID: strconv.Itoa(screen.id), + Name: name, + ScaleFactor: float32(math.Round(float64(screen.s)*100) / 100), + X: x, + Y: y, + Size: application.Size{Width: w, Height: h}, + Bounds: application.Rect{X: x, Y: y, Width: w, Height: h}, + PhysicalBounds: application.Rect{X: x, Y: y, Width: w, Height: h}, + WorkArea: application.Rect{X: x, Y: y, Width: w, Height: h - int(40*screen.s)}, + PhysicalWorkArea: application.Rect{X: x, Y: y, Width: w, Height: h - int(40*screen.s)}, + IsPrimary: screen.id == 1, + Rotation: 0, + }) + } + return ParsedLayout{ + name: layout.name, + screens: screens, + } +} + +func matchRects(r1, r2 application.Rect) error { + threshold := 1.0 + if math.Abs(float64(r1.X-r2.X)) > threshold || + math.Abs(float64(r1.Y-r2.Y)) > threshold || + math.Abs(float64(r1.Width-r2.Width)) > threshold || + math.Abs(float64(r1.Height-r2.Height)) > threshold { + return fmt.Errorf("%v != %v", r1, r2) + } + return nil +} + +// Test screens layout (DPI transformation) +func TestScreenManager_ScreensLayout(t *testing.T) { + sm := application.ScreenManager{} + + t.Run("Child scaled", func(t *testing.T) { + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + screens := sm.GetAll() + is.Equal(len(screens), 2) // 2 screens + is.Equal(screens[0].PhysicalBounds, application.Rect{X: 0, Y: 0, Width: 1200, Height: 1200}) // Parent physical bounds + is.Equal(screens[0].Bounds, screens[0].PhysicalBounds) // Parent no scaling + is.Equal(screens[1].PhysicalBounds, application.Rect{X: 1200, Y: 600, Width: 1200, Height: 1200}) // Child physical bounds + is.Equal(screens[1].Bounds, application.Rect{X: 1200, Y: 600, Width: 800, Height: 800}) // Child DIP bounds + }) + + t.Run("Parent scaled", func(t *testing.T) { + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + screens := sm.GetAll() + is.Equal(len(screens), 2) // 2 screens + is.Equal(screens[0].PhysicalBounds, application.Rect{X: 0, Y: 0, Width: 1200, Height: 1200}) // Parent physical bounds + is.Equal(screens[0].Bounds, application.Rect{X: 0, Y: 0, Width: 800, Height: 800}) // Parent DIP bounds + is.Equal(screens[1].PhysicalBounds, application.Rect{X: 1200, Y: 600, Width: 1200, Height: 1200}) // Child physical bounds + is.Equal(screens[1].Bounds, application.Rect{X: 800, Y: 400, Width: 1200, Height: 1200}) // Child DIP bounds + }) +} + +// Test basic transformation between physical and DIP coordinates +func TestScreenManager_BasicTranformation(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1200, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + pt := application.Point{X: 100, Y: 100} + is.Equal(sm.DipToPhysicalPoint(pt), pt) // DipToPhysicalPoint screen1 + is.Equal(sm.PhysicalToDipPoint(pt), pt) // PhysicalToDipPoint screen1 + + ptDip := application.Point{X: 1300, Y: 700} + ptPhysical := application.Point{X: 1350, Y: 750} + is.Equal(sm.DipToPhysicalPoint(ptDip), ptPhysical) // DipToPhysicalPoint screen2 + is.Equal(sm.PhysicalToDipPoint(ptPhysical), ptDip) // PhysicalToDipPoint screen2 + + rect := application.Rect{X: 100, Y: 100, Width: 200, Height: 300} + is.Equal(sm.DipToPhysicalRect(rect), rect) // DipToPhysicalRect screen1 + is.Equal(sm.PhysicalToDipRect(rect), rect) // DipToPhysicalRect screen1 + + rectDip := application.Rect{X: 1300, Y: 700, Width: 200, Height: 300} + rectPhysical := application.Rect{X: 1350, Y: 750, Width: 300, Height: 450} + is.Equal(sm.DipToPhysicalRect(rectDip), rectPhysical) // DipToPhysicalRect screen2 + is.Equal(sm.PhysicalToDipRect(rectPhysical), rectDip) // DipToPhysicalRect screen2 + + rectDip = application.Rect{X: 2200, Y: 250, Width: 200, Height: 300} + rectPhysical = application.Rect{X: 2700, Y: 75, Width: 300, Height: 450} + is.Equal(sm.DipToPhysicalRect(rectDip), rectPhysical) // DipToPhysicalRect outside screen2 + is.Equal(sm.PhysicalToDipRect(rectPhysical), rectDip) // DipToPhysicalRect outside screen2 +} + +func TestScreenManager_PrimaryScreen(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + is.Equal(sm.GetPrimary(), layout.screens[0]) // Primary screen + } + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 1200, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 600}}, + }}) + + layout.screens[0], layout.screens[1] = layout.screens[1], layout.screens[0] + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + is.Equal(sm.GetPrimary(), layout.screens[1]) // Primary screen + + layout.screens[1].IsPrimary = false + err = sm.LayoutScreens(layout.screens) + is.True(err != nil) // Should error when no primary screen found +} + +// Test edge alignment between transformation +// (points and rects on the screen edge should transform to the same precise edge position) +func TestScreenManager_EdgeAlign(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + ptOriginDip := screen.Bounds.Origin() + ptOriginPhysical := screen.PhysicalBounds.Origin() + ptCornerDip := screen.Bounds.InsideCorner() + ptCornerPhysical := screen.PhysicalBounds.InsideCorner() + + is.Equal(sm.DipToPhysicalPoint(ptOriginDip), ptOriginPhysical) // DipToPhysicalPoint Origin + is.Equal(sm.PhysicalToDipPoint(ptOriginPhysical), ptOriginDip) // PhysicalToDipPoint Origin + is.Equal(sm.DipToPhysicalPoint(ptCornerDip), ptCornerPhysical) // DipToPhysicalPoint Corner + is.Equal(sm.PhysicalToDipPoint(ptCornerPhysical), ptCornerDip) // PhysicalToDipPoint Corner + + rectOriginDip := application.Rect{X: ptOriginDip.X, Y: ptOriginDip.Y, Width: 100, Height: 100} + rectOriginPhysical := application.Rect{X: ptOriginPhysical.X, Y: ptOriginPhysical.Y, Width: 100, Height: 100} + rectCornerDip := application.Rect{X: ptCornerDip.X - 99, Y: ptCornerDip.Y - 99, Width: 100, Height: 100} + rectCornerPhysical := application.Rect{X: ptCornerPhysical.X - 99, Y: ptCornerPhysical.Y - 99, Width: 100, Height: 100} + + is.Equal(sm.DipToPhysicalRect(rectOriginDip).Origin(), rectOriginPhysical.Origin()) // DipToPhysicalRect Origin + is.Equal(sm.PhysicalToDipRect(rectOriginPhysical).Origin(), rectOriginDip.Origin()) // PhysicalToDipRect Origin + is.Equal(sm.DipToPhysicalRect(rectCornerDip).Corner(), rectCornerPhysical.Corner()) // DipToPhysicalRect Corner + is.Equal(sm.PhysicalToDipRect(rectCornerPhysical).Corner(), rectCornerDip.Corner()) // PhysicalToDipRect Corner + } + } +} + +func TestScreenManager_ProbePoints(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + threshold := 1.0 + steps := 3 + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + for i := 0; i <= 1; i++ { + isDip := (i == 0) + + var b application.Rect + if isDip { + b = screen.Bounds + } else { + b = screen.PhysicalBounds + } + + xStep := b.Width / steps + yStep := b.Height / steps + if xStep < 1 { + xStep = 1 + } + if yStep < 1 { + yStep = 1 + } + pt := b.Origin() + xDone := false + yDone := false + + for !yDone { + if pt.Y > b.InsideCorner().Y { + pt.Y = b.InsideCorner().Y + yDone = true + } + + pt.X = b.X + xDone = false + + for !xDone { + if pt.X > b.InsideCorner().X { + pt.X = b.InsideCorner().X + xDone = true + } + var ptDblTransformed application.Point + + if isDip { + ptDblTransformed = sm.PhysicalToDipPoint(sm.DipToPhysicalPoint(pt)) + } else { + ptDblTransformed = sm.DipToPhysicalPoint(sm.PhysicalToDipPoint(pt)) + } + + is.True(math.Abs(float64(ptDblTransformed.X-pt.X)) <= threshold) + is.True(math.Abs(float64(ptDblTransformed.Y-pt.Y)) <= threshold) + pt.X += xStep + } + pt.Y += yStep + } + } + } + } +} + +// Test transformation drift over time +func TestScreenManager_TransformationDrift(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + for _, layout := range exampleLayouts() { + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + for _, screen := range sm.GetAll() { + rectPhysicalOriginal := application.Rect{ + X: screen.PhysicalBounds.X + 100, + Y: screen.PhysicalBounds.Y + 100, + Width: 123, + Height: 123, + } + + // Slide the position to catch any rounding errors + for i := 0; i < 10; i++ { + rectPhysicalOriginal.X++ + rectPhysicalOriginal.Y++ + rectPhysical := rectPhysicalOriginal + // Transform back and forth several times to make sure no drift is introduced over time + for j := 0; j < 10; j++ { + rectDip := sm.PhysicalToDipRect(rectPhysical) + rectPhysical = sm.DipToPhysicalRect(rectDip) + } + is.NoErr(matchRects(rectPhysical, rectPhysicalOriginal)) + } + } + } +} + +func TestScreenManager_ScreenNearestRect(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 3840, h: 2160, s: 163.0 / 96, name: `27" 4K UHD 163DPI`}, + {id: 2, w: 1920, h: 1080, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}, name: `23" FHD 96DPI`}, + {id: 3, w: 1920, h: 1080, s: 1.25, parent: ScreenDefParent{id: 1, align: "l", offset: 0}, name: `23" FHD 96DPI (125%)`}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + type Rects map[string][]application.Rect + + t.Run("DIP rects", func(t *testing.T) { + is := is.New(t) + rects := Rects{ + "1": []application.Rect{ + {X: -150, Y: 260, Width: 400, Height: 300}, + {X: -250, Y: 750, Width: 400, Height: 300}, + {X: -450, Y: 950, Width: 400, Height: 300}, + {X: 800, Y: 1350, Width: 400, Height: 300}, + {X: 2000, Y: 100, Width: 400, Height: 300}, + {X: 2100, Y: 950, Width: 400, Height: 300}, + {X: 2350, Y: 1200, Width: 400, Height: 300}, + }, + "2": []application.Rect{ + {X: 2100, Y: 50, Width: 400, Height: 300}, + {X: 2150, Y: 950, Width: 400, Height: 300}, + {X: 2450, Y: 1150, Width: 400, Height: 300}, + {X: 4300, Y: 400, Width: 400, Height: 300}, + }, + "3": []application.Rect{ + {X: -2000, Y: 100, Width: 400, Height: 300}, + {X: -220, Y: 200, Width: 400, Height: 300}, + {X: -300, Y: 750, Width: 400, Height: 300}, + {X: -500, Y: 900, Width: 400, Height: 300}, + }, + } + + for screenID, screenRects := range rects { + for _, rect := range screenRects { + screen := sm.ScreenNearestDipRect(rect) + is.Equal(screen.ID, screenID) + } + } + }) + t.Run("Physical rects", func(t *testing.T) { + is := is.New(t) + rects := Rects{ + "1": []application.Rect{ + {X: -150, Y: 100, Width: 400, Height: 300}, + {X: -250, Y: 1500, Width: 400, Height: 300}, + {X: 3600, Y: 100, Width: 400, Height: 300}, + }, + "2": []application.Rect{ + {X: 3700, Y: 100, Width: 400, Height: 300}, + {X: 4000, Y: 1150, Width: 400, Height: 300}, + }, + "3": []application.Rect{ + {X: -250, Y: 100, Width: 400, Height: 300}, + {X: -300, Y: 950, Width: 400, Height: 300}, + {X: -1000, Y: 1000, Width: 400, Height: 300}, + }, + } + + for screenID, screenRects := range rects { + for _, rect := range screenRects { + screen := sm.ScreenNearestPhysicalRect(rect) + is.Equal(screen.ID, screenID) + } + } + }) + + // DIP rect is near screen1 but when transformed becomes near screen2. + // To have a consistent transformation back & forth, screen nearest physical rect + // should be the one given by ScreenNearestDipRect + t.Run("Edge case 1", func(t *testing.T) { + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1300, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: -20}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectDip := application.Rect{X: 1020, Y: 800, Width: 400, Height: 300} + rectPhysical := sm.DipToPhysicalRect(rectDip) + + screenDip := sm.ScreenNearestDipRect(rectDip) + screenPhysical := sm.ScreenNearestPhysicalRect(rectPhysical) + is.Equal(screenDip.ID, "2") // screenDip + is.Equal(screenPhysical.ID, "2") // screenPhysical + + rectDblTransformed := sm.PhysicalToDipRect(rectPhysical) + is.NoErr(matchRects(rectDblTransformed, rectDip)) // double transformation + }) +} + +// Unsolved edge cases +func TestScreenManager_UnsolvedEdgeCases(t *testing.T) { + sm := application.ScreenManager{} + is := is.New(t) + + // Edge case 1: invalid DIP rect location + // there could be a setup where some dip rects locations are invalid, meaning that there's no + // physical rect that could produce that dip rect at this location + // Not sure how to solve this scenario + t.Run("Edge case 1: invalid dip rect", func(t *testing.T) { + t.Skip("Unsolved edge case") + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1}, + {id: 2, w: 1200, h: 1100, s: 1.5, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectDip := application.Rect{X: 1050, Y: 700, Width: 400, Height: 300} + rectPhysical := sm.DipToPhysicalRect(rectDip) + + screenDip := sm.ScreenNearestDipRect(rectDip) + screenPhysical := sm.ScreenNearestPhysicalRect(rectPhysical) + is.Equal(screenDip.ID, screenPhysical.ID) + + rectDblTransformed := sm.PhysicalToDipRect(rectPhysical) + is.NoErr(matchRects(rectDblTransformed, rectDip)) // double transformation + }) + + // Edge case 2: physical rect that changes when double transformed + // there could be a setup where a dip rect at some locations could be produced by two different physical rects + // causing one of these physical rects to be changed to the other when double transformed + // Not sure how to solve this scenario + t.Run("Edge case 2: changed physical rect", func(t *testing.T) { + t.Skip("Unsolved edge case") + is := is.New(t) + layout := parseLayout(ScreensLayout{screens: []ScreenDef{ + {id: 1, w: 1200, h: 1200, s: 1.5}, + {id: 2, w: 1200, h: 900, s: 1, parent: ScreenDefParent{id: 1, align: "r", offset: 0}}, + }}) + err := sm.LayoutScreens(layout.screens) + is.NoErr(err) + + rectPhysical := application.Rect{X: 1050, Y: 890, Width: 400, Height: 300} + rectDblTransformed := sm.DipToPhysicalRect(sm.PhysicalToDipRect(rectPhysical)) + is.NoErr(matchRects(rectDblTransformed, rectPhysical)) // double transformation + }) +} + +func BenchmarkScreenManager_LayoutScreens(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.LayoutScreens(screens) + } +} + +func BenchmarkScreenManager_TransformPoint(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + sm.LayoutScreens(screens) + + pt := application.Point{X: 500, Y: 500} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.DipToPhysicalPoint(pt) + } +} + +func BenchmarkScreenManager_TransformRect(b *testing.B) { + sm := application.ScreenManager{} + layouts := exampleLayouts() + screens := layouts[3].screens + sm.LayoutScreens(screens) + + rect := application.Rect{X: 500, Y: 500, Width: 800, Height: 600} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sm.DipToPhysicalRect(rect) + } +} diff --git a/v3/pkg/application/services.go b/v3/pkg/application/services.go new file mode 100644 index 000000000..582d135b0 --- /dev/null +++ b/v3/pkg/application/services.go @@ -0,0 +1,134 @@ +package application + +import ( + "context" + "reflect" +) + +// Service wraps a bound type instance. +// The zero value of Service is invalid. +// Valid values may only be obtained by calling [NewService]. +type Service struct { + instance any + options ServiceOptions +} + +// ServiceOptions provides optional parameters for calls to [NewService]. +type ServiceOptions struct { + // Name can be set to override the name of the service + // for logging and debugging purposes. + // + // If empty, it will default + // either to the value obtained through the [ServiceName] interface, + // or to the type name. + Name string + + // If the service instance implements [http.Handler], + // it will be mounted on the internal asset server + // at the prefix specified by Route. + Route string + + // MarshalError will be called if non-nil + // to marshal to JSON the error values returned by this service's methods. + // + // MarshalError is not allowed to fail, + // but it may return a nil slice to fall back + // to the globally configured error handler. + // + // If the returned slice is not nil, it must contain valid JSON. + MarshalError func(error) []byte +} + +// DefaultServiceOptions specifies the default values of service options, +// used when no [ServiceOptions] instance is provided to [NewService]. +var DefaultServiceOptions = ServiceOptions{} + +// NewService returns a Service value wrapping the given pointer. +// If T is not a concrete named type, the returned value is invalid. +func NewService[T any](instance *T) Service { + return Service{instance, DefaultServiceOptions} +} + +// NewServiceWithOptions returns a Service value wrapping the given pointer +// and specifying the given service options. +// If T is not a concrete named type, the returned value is invalid. +func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service { + service := NewService(instance) // Delegate to NewService so that the static analyser may detect T. Do not remove this call. + service.options = options + return service +} + +// Instance returns the service instance provided to [NewService]. +func (s Service) Instance() any { + return s.instance +} + +// ServiceName returns the name of the service +// +// This is an *optional* method that may be implemented by service instances. +// It is used for logging and debugging purposes. +// +// If a non-empty name is provided with [ServiceOptions], +// it takes precedence over the one returned by the ServiceName method. +type ServiceName interface { + ServiceName() string +} + +// ServiceStartup is an *optional* method that may be implemented by service instances. +// +// This method will be called during application startup and will receive a copy of the options +// specified at creation time. It can be used for initialising resources. +// +// The context will be valid as long as the application is running, +// and will be canceled right before shutdown. +// +// Services are guaranteed to receive the startup notification +// in the exact order in which they were either +// listed in [Options.Services] or registered with [App.RegisterService], +// with those from [Options.Services] coming first. +// +// If the return value is non-nil, the startup process aborts +// and [App.Run] returns the error wrapped with [fmt.Errorf] +// in a user-friendly message comprising the service name. +// The original error can be retrieved either by calling the Unwrap method +// or through the [errors.As] API. +// +// When that happens, service instances that have been already initialised +// receive a shutdown notification. +type ServiceStartup interface { + ServiceStartup(ctx context.Context, options ServiceOptions) error +} + +// ServiceShutdown is an *optional* method that may be implemented by service instances. +// +// This method will be called during application shutdown. It can be used for cleaning up resources. +// If a service has received a startup notification, +// then it is guaranteed to receive a shutdown notification too, +// except in case of unhandled panics during shutdown. +// +// Services receive shutdown notifications in reverse registration order, +// after all user-provided shutdown hooks have run (see [App.OnShutdown]). +// +// If the return value is non-nil, it is passed to the application's +// configured error handler at [Options.ErrorHandler], +// wrapped with [fmt.Errorf] in a user-friendly message comprising the service name. +// The default behaviour is to log the error along with the service name. +// The original error can be retrieved either by calling the Unwrap method +// or through the [errors.As] API. +type ServiceShutdown interface { + ServiceShutdown() error +} + +func getServiceName(service Service) string { + if service.options.Name != "" { + return service.options.Name + } + + // Check if the service implements the ServiceName interface + if s, ok := service.Instance().(ServiceName); ok { + return s.ServiceName() + } + + // Finally, get the name from the type. + return reflect.TypeOf(service.Instance()).Elem().String() +} diff --git a/v3/pkg/application/single_instance.go b/v3/pkg/application/single_instance.go new file mode 100644 index 000000000..24bbf5c31 --- /dev/null +++ b/v3/pkg/application/single_instance.go @@ -0,0 +1,215 @@ +package application + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/json" + "errors" + "os" + "path/filepath" + "sync" +) + +var alreadyRunningError = errors.New("application is already running") +var secondInstanceBuffer = make(chan string, 1) +var once sync.Once + +// SecondInstanceData contains information about the second instance launch +type SecondInstanceData struct { + Args []string `json:"args"` + WorkingDir string `json:"workingDir"` + AdditionalData map[string]string `json:"additionalData,omitempty"` +} + +// SingleInstanceOptions defines options for single instance functionality +type SingleInstanceOptions struct { + // UniqueID is used to identify the application instance + // This should be unique per application, e.g. "com.myapp.myapplication" + UniqueID string + + // OnSecondInstanceLaunch is called when a second instance of the application is launched + // The callback receives data about the second instance launch + OnSecondInstanceLaunch func(data SecondInstanceData) + + // AdditionalData allows passing custom data from second instance to first + AdditionalData map[string]string + + // ExitCode is the exit code to use when the second instance exits + ExitCode int + + // EncryptionKey is a 32-byte key used for encrypting instance communication + // If not provided (zero array), data will be sent unencrypted + EncryptionKey [32]byte +} + +// platformLock is the interface that platform-specific lock implementations must implement +type platformLock interface { + // acquire attempts to acquire the lock + acquire(uniqueID string) error + // release releases the lock and cleans up resources + release() + // notify sends data to the first instance + notify(data string) error +} + +// singleInstanceManager handles the single instance functionality +type singleInstanceManager struct { + options *SingleInstanceOptions + lock platformLock + app *App +} + +func newSingleInstanceManager(app *App, options *SingleInstanceOptions) (*singleInstanceManager, error) { + if options == nil { + return nil, nil + } + + manager := &singleInstanceManager{ + options: options, + app: app, + } + + // Launch second instance data listener + once.Do(func() { + go func() { + defer handlePanic() + for encryptedData := range secondInstanceBuffer { + var secondInstanceData SecondInstanceData + var jsonData []byte + var err error + + // Check if encryption key is non-zero + var zeroKey [32]byte + if options.EncryptionKey != zeroKey { + // Try to decrypt the data + jsonData, err = decrypt(options.EncryptionKey, encryptedData) + if err != nil { + continue // Skip invalid data + } + } else { + jsonData = []byte(encryptedData) + } + + if err := json.Unmarshal(jsonData, &secondInstanceData); err == nil && manager.options.OnSecondInstanceLaunch != nil { + manager.options.OnSecondInstanceLaunch(secondInstanceData) + } + } + }() + }) + + // Create platform-specific lock + lock, err := newPlatformLock(manager) + if err != nil { + return nil, err + } + + manager.lock = lock + + // Try to acquire the lock + err = lock.acquire(options.UniqueID) + if err != nil { + return manager, err + } + + return manager, nil +} + +func (m *singleInstanceManager) cleanup() { + if m == nil || m.lock == nil { + return + } + m.lock.release() +} + +// encrypt encrypts data using AES-256-GCM +func encrypt(key [32]byte, plaintext []byte) (string, error) { + block, err := aes.NewCipher(key[:]) + if err != nil { + return "", err + } + + nonce := make([]byte, 12) + if _, err := rand.Read(nonce); err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + encrypted := append(nonce, ciphertext...) + return base64.StdEncoding.EncodeToString(encrypted), nil +} + +// decrypt decrypts data using AES-256-GCM +func decrypt(key [32]byte, encrypted string) ([]byte, error) { + data, err := base64.StdEncoding.DecodeString(encrypted) + if err != nil { + return nil, err + } + + if len(data) < 12 { + return nil, errors.New("invalid encrypted data") + } + + block, err := aes.NewCipher(key[:]) + if err != nil { + return nil, err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := data[:12] + ciphertext := data[12:] + + return aesgcm.Open(nil, nonce, ciphertext, nil) +} + +// notifyFirstInstance sends data to the first instance of the application +func (m *singleInstanceManager) notifyFirstInstance() error { + data := SecondInstanceData{ + Args: os.Args, + WorkingDir: getCurrentWorkingDir(), + AdditionalData: m.options.AdditionalData, + } + + serialized, err := json.Marshal(data) + if err != nil { + return err + } + + // Check if encryption key is non-zero + var zeroKey [32]byte + if m.options.EncryptionKey != zeroKey { + encrypted, err := encrypt(m.options.EncryptionKey, serialized) + if err != nil { + return err + } + return m.lock.notify(encrypted) + } + + return m.lock.notify(string(serialized)) +} + +func getCurrentWorkingDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} + +// getLockPath returns the path to the lock file for Unix systems +func getLockPath(uniqueID string) string { + // Use system temp directory + tmpDir := os.TempDir() + lockFileName := uniqueID + ".lock" + return filepath.Join(tmpDir, lockFileName) +} diff --git a/v3/pkg/application/single_instance_darwin.go b/v3/pkg/application/single_instance_darwin.go new file mode 100644 index 000000000..4101294d8 --- /dev/null +++ b/v3/pkg/application/single_instance_darwin.go @@ -0,0 +1,96 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa + +#include +#import +#import + +static void SendDataToFirstInstance(char *singleInstanceUniqueId, char* message) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId] + object:nil + userInfo:@{@"message": [NSString stringWithUTF8String:message]} + deliverImmediately:YES]; +} + +*/ +import "C" +import ( + "os" + "syscall" + "unsafe" +) + +type darwinLock struct { + file *os.File + uniqueID string + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &darwinLock{ + manager: manager, + }, nil +} + +func (l *darwinLock) acquire(uniqueID string) error { + l.uniqueID = uniqueID + lockFilePath := os.TempDir() + lockFileName := uniqueID + ".lock" + var err error + l.file, err = createLockFile(lockFilePath + "/" + lockFileName) + if err != nil { + return alreadyRunningError + } + return nil +} + +func (l *darwinLock) release() { + if l.file != nil { + syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) + l.file.Close() + os.Remove(l.file.Name()) + l.file = nil + } +} + +func (l *darwinLock) notify(data string) error { + singleInstanceUniqueId := C.CString(l.uniqueID) + defer C.free(unsafe.Pointer(singleInstanceUniqueId)) + cData := C.CString(data) + defer C.free(unsafe.Pointer(cData)) + + C.SendDataToFirstInstance(singleInstanceUniqueId, cData) + + os.Exit(l.manager.options.ExitCode) + return nil +} + +// CreateLockFile tries to create a file with given name and acquire an +// exclusive lock on it. If the file already exists AND is still locked, it will +// fail. +func createLockFile(filename string) (*os.File, error) { + file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + + err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + if err != nil { + file.Close() + return nil, err + } + + return file, nil +} + +//export handleSecondInstanceData +func handleSecondInstanceData(secondInstanceMessage *C.char) { + message := C.GoString(secondInstanceMessage) + secondInstanceBuffer <- message +} diff --git a/v3/pkg/application/single_instance_linux.go b/v3/pkg/application/single_instance_linux.go new file mode 100644 index 000000000..28c9e5483 --- /dev/null +++ b/v3/pkg/application/single_instance_linux.go @@ -0,0 +1,101 @@ +//go:build linux + +package application + +import ( + "errors" + "os" + "strings" + "sync" + "syscall" + + "github.com/godbus/dbus/v5" +) + +type dbusHandler func(string) + +var setup sync.Once + +func (f dbusHandler) SendMessage(message string) *dbus.Error { + f(message) + return nil +} + +type linuxLock struct { + file *os.File + uniqueID string + dbusPath string + dbusName string + manager *singleInstanceManager +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &linuxLock{ + manager: manager, + }, nil +} + +func (l *linuxLock) acquire(uniqueID string) error { + if uniqueID == "" { + return errors.New("UniqueID is required for single instance lock") + } + + id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_") + + l.dbusName = "org." + id + ".SingleInstance" + l.dbusPath = "/org/" + id + "/SingleInstance" + + conn, err := dbus.ConnectSessionBus() + // if we will reach any error during establishing connection or sending message we will just continue. + // It should not be the case that such thing will happen actually, but just in case. + if err != nil { + return err + } + + setup.Do(func() { + f := dbusHandler(func(message string) { + secondInstanceBuffer <- message + }) + + err = conn.Export(f, dbus.ObjectPath(l.dbusPath), l.dbusName) + }) + if err != nil { + return err + } + + reply, err := conn.RequestName(l.dbusName, dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + + // if name already taken, try to send args to existing instance, if no success just launch new instance + if reply == dbus.RequestNameReplyExists { + return alreadyRunningError + } + return nil +} + +func (l *linuxLock) release() { + if l.file != nil { + syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) + l.file.Close() + os.Remove(l.file.Name()) + l.file = nil + } +} + +func (l *linuxLock) notify(data string) error { + conn, err := dbus.ConnectSessionBus() + // if we will reach any error during establishing connection or sending message we will just continue. + // It should not be the case that such thing will happen actually, but just in case. + if err != nil { + return err + } + + err = conn.Object(l.dbusName, dbus.ObjectPath(l.dbusPath)).Call(l.dbusName+".SendMessage", 0, data).Store() + if err != nil { + return err + } + os.Exit(l.manager.options.ExitCode) + return nil +} diff --git a/v3/pkg/application/single_instance_windows.go b/v3/pkg/application/single_instance_windows.go new file mode 100644 index 000000000..b92b2749a --- /dev/null +++ b/v3/pkg/application/single_instance_windows.go @@ -0,0 +1,129 @@ +//go:build windows + +package application + +import ( + "errors" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows" +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") +) + +type windowsLock struct { + handle syscall.Handle + uniqueID string + msgString string + hwnd w32.HWND + manager *singleInstanceManager + className string + windowName string +} + +func newPlatformLock(manager *singleInstanceManager) (platformLock, error) { + return &windowsLock{ + manager: manager, + }, nil +} + +func (l *windowsLock) acquire(uniqueID string) error { + if uniqueID == "" { + return errors.New("UniqueID is required for single instance lock") + } + + l.uniqueID = uniqueID + id := "wails-app-" + uniqueID + l.className = id + "-sic" + l.windowName = id + "-siw" + mutexName := id + "-sim" + + _, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName)) + if err != nil { + // Find the window + return alreadyRunningError + } else { + l.hwnd = createEventTargetWindow(l.className, l.windowName) + } + + return nil +} + +func (l *windowsLock) release() { + if l.handle != 0 { + syscall.CloseHandle(l.handle) + l.handle = 0 + } + if l.hwnd != 0 { + w32.DestroyWindow(l.hwnd) + l.hwnd = 0 + } +} + +func (l *windowsLock) notify(data string) error { + + // app is already running + hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(l.className), windows.StringToUTF16Ptr(l.windowName)) + + if hwnd == 0 { + return errors.New("unable to notify other instance") + } + + w32.SendMessageToWindow(hwnd, data) + + return nil +} + +func createEventTargetWindow(className string, windowName string) w32.HWND { + var class w32.WNDCLASSEX + class.Size = uint32(unsafe.Sizeof(class)) + class.Style = 0 + class.WndProc = syscall.NewCallback(wndProc) + class.ClsExtra = 0 + class.WndExtra = 0 + class.Instance = w32.GetModuleHandle("") + class.Icon = 0 + class.Cursor = 0 + class.Background = 0 + class.MenuName = nil + class.ClassName = w32.MustStringToUTF16Ptr(className) + class.IconSm = 0 + + w32.RegisterClassEx(&class) + + // Create hidden message-only window + hwnd := w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(className), + w32.MustStringToUTF16Ptr(windowName), + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + w32.GetModuleHandle(""), + nil, + ) + + return hwnd +} + +func wndProc(hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM) w32.LRESULT { + if msg == w32.WM_COPYDATA { + ldata := (*w32.COPYDATASTRUCT)(unsafe.Pointer(lparam)) + + if ldata.DwData == w32.WMCOPYDATA_SINGLE_INSTANCE_DATA { + serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.LpData))) + secondInstanceBuffer <- serialized + } + return w32.LRESULT(0) + } + + return w32.DefWindowProc(hwnd, msg, wparam, lparam) +} diff --git a/v3/pkg/application/system_tray_manager.go b/v3/pkg/application/system_tray_manager.go new file mode 100644 index 000000000..16bdf5153 --- /dev/null +++ b/v3/pkg/application/system_tray_manager.go @@ -0,0 +1,44 @@ +package application + +// SystemTrayManager manages system tray-related operations +type SystemTrayManager struct { + app *App +} + +// newSystemTrayManager creates a new SystemTrayManager instance +func newSystemTrayManager(app *App) *SystemTrayManager { + return &SystemTrayManager{ + app: app, + } +} + +// New creates a new system tray +func (stm *SystemTrayManager) New() *SystemTray { + id := stm.getNextID() + newSystemTray := newSystemTray(id) + + stm.app.systemTraysLock.Lock() + stm.app.systemTrays[id] = newSystemTray + stm.app.systemTraysLock.Unlock() + + stm.app.runOrDeferToAppRun(newSystemTray) + + return newSystemTray +} + +// getNextID generates the next system tray ID (internal use) +func (stm *SystemTrayManager) getNextID() uint { + stm.app.systemTrayIDLock.Lock() + defer stm.app.systemTrayIDLock.Unlock() + stm.app.systemTrayID++ + return stm.app.systemTrayID +} + +// destroy destroys a system tray (internal use) +func (stm *SystemTrayManager) destroy(tray *SystemTray) { + // Remove the system tray from the app.systemTrays map + stm.app.systemTraysLock.Lock() + delete(stm.app.systemTrays, tray.id) + stm.app.systemTraysLock.Unlock() + tray.destroy() +} diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go new file mode 100644 index 000000000..beb61d0d9 --- /dev/null +++ b/v3/pkg/application/systemtray.go @@ -0,0 +1,333 @@ +package application + +import ( + "errors" + "runtime" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +type IconPosition int + +const ( + NSImageNone = iota + NSImageOnly + NSImageLeft + NSImageRight + NSImageBelow + NSImageAbove + NSImageOverlaps + NSImageLeading + NSImageTrailing +) + +type systemTrayImpl interface { + setLabel(label string) + setTooltip(tooltip string) + run() + setIcon(icon []byte) + setMenu(menu *Menu) + setIconPosition(position IconPosition) + setTemplateIcon(icon []byte) + destroy() + setDarkModeIcon(icon []byte) + bounds() (*Rect, error) + getScreen() (*Screen, error) + positionWindow(window *WebviewWindow, offset int) error + openMenu() + Show() + Hide() +} + +type SystemTray struct { + id uint + label string + tooltip string + icon []byte + darkModeIcon []byte + iconPosition IconPosition + + clickHandler func() + rightClickHandler func() + doubleClickHandler func() + rightDoubleClickHandler func() + mouseEnterHandler func() + mouseLeaveHandler func() + onMenuOpen func() + onMenuClose func() + + // Platform specific implementation + impl systemTrayImpl + menu *Menu + isTemplateIcon bool + attachedWindow WindowAttachConfig +} + +func newSystemTray(id uint) *SystemTray { + result := &SystemTray{ + id: id, + label: "", + tooltip: "", + iconPosition: NSImageLeading, + attachedWindow: WindowAttachConfig{ + Window: nil, + Offset: 0, + Debounce: 200 * time.Millisecond, + }, + } + result.clickHandler = result.defaultClickHandler + return result +} + +func (s *SystemTray) SetLabel(label string) { + if s.impl == nil { + s.label = label + return + } + InvokeSync(func() { + s.impl.setLabel(label) + }) +} + +func (s *SystemTray) Label() string { + return s.label +} + +func (s *SystemTray) Run() { + s.impl = newSystemTrayImpl(s) + + if s.attachedWindow.Window != nil { + // Setup listener + s.attachedWindow.Window.OnWindowEvent(events.Common.WindowLostFocus, func(event *WindowEvent) { + s.attachedWindow.Window.Hide() + // Special handler for Windows + if runtime.GOOS == "windows" { + // We don't do this unless the window has already been shown + if s.attachedWindow.hasBeenShown == false { + return + } + s.attachedWindow.justClosed = true + go func() { + defer handlePanic() + time.Sleep(s.attachedWindow.Debounce) + s.attachedWindow.justClosed = false + }() + } + }) + } + + InvokeSync(s.impl.run) +} + +func (s *SystemTray) PositionWindow(window *WebviewWindow, offset int) error { + if s.impl == nil { + return errors.New("system tray not running") + } + return InvokeSyncWithError(func() error { + return s.impl.positionWindow(window, offset) + }) +} + +func (s *SystemTray) SetIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + } else { + InvokeSync(func() { + s.impl.setIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetDarkModeIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.darkModeIcon = icon + } else { + InvokeSync(func() { + s.impl.setDarkModeIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetMenu(menu *Menu) *SystemTray { + if s.impl == nil { + s.menu = menu + } else { + InvokeSync(func() { + s.impl.setMenu(menu) + }) + } + return s +} + +func (s *SystemTray) SetIconPosition(iconPosition IconPosition) *SystemTray { + if s.impl == nil { + s.iconPosition = iconPosition + } else { + InvokeSync(func() { + s.impl.setIconPosition(iconPosition) + }) + } + return s +} + +func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + s.isTemplateIcon = true + } else { + InvokeSync(func() { + s.impl.setTemplateIcon(icon) + }) + } + return s +} + +func (s *SystemTray) SetTooltip(tooltip string) { + if s.impl == nil { + s.tooltip = tooltip + return + } + InvokeSync(func() { + s.impl.setTooltip(tooltip) + }) +} + +func (s *SystemTray) Destroy() { + globalApplication.SystemTray.destroy(s) +} + +func (s *SystemTray) destroy() { + if s.impl == nil { + return + } + s.impl.destroy() +} + +func (s *SystemTray) OnClick(handler func()) *SystemTray { + s.clickHandler = handler + return s +} + +func (s *SystemTray) OnRightClick(handler func()) *SystemTray { + s.rightClickHandler = handler + return s +} + +func (s *SystemTray) OnDoubleClick(handler func()) *SystemTray { + s.doubleClickHandler = handler + return s +} + +func (s *SystemTray) OnRightDoubleClick(handler func()) *SystemTray { + s.rightDoubleClickHandler = handler + return s +} + +func (s *SystemTray) OnMouseEnter(handler func()) *SystemTray { + s.mouseEnterHandler = handler + return s +} + +func (s *SystemTray) OnMouseLeave(handler func()) *SystemTray { + s.mouseLeaveHandler = handler + return s +} + +func (s *SystemTray) Show() { + if s.impl == nil { + return + } + InvokeSync(func() { + s.impl.Show() + }) +} + +func (s *SystemTray) Hide() { + if s.impl == nil { + return + } + InvokeSync(func() { + s.impl.Hide() + }) +} + +type WindowAttachConfig struct { + // Window is the window to attach to the system tray. If it's null, the request to attach will be ignored. + Window *WebviewWindow + + // Offset indicates the gap in pixels between the system tray and the window + Offset int + + // Debounce is used by Windows to indicate how long to wait before responding to a mouse + // up event on the notification icon. See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked + Debounce time.Duration + + // Indicates that the window has just been closed + justClosed bool + + // Indicates that the window has been shown a first time + hasBeenShown bool + + // Used to ensure that the window state is read on first click + initialClick sync.Once +} + +// AttachWindow attaches a window to the system tray. The window will be shown when the system tray icon is clicked. +// The window will be hidden when the system tray icon is clicked again, or when the window loses focus. +func (s *SystemTray) AttachWindow(window *WebviewWindow) *SystemTray { + s.attachedWindow.Window = window + return s +} + +// WindowOffset sets the gap in pixels between the system tray and the window +func (s *SystemTray) WindowOffset(offset int) *SystemTray { + s.attachedWindow.Offset = offset + return s +} + +// WindowDebounce is used by Windows to indicate how long to wait before responding to a mouse +// up event on the notification icon. This prevents the window from being hidden and then immediately +// shown when the user clicks on the system tray icon. +// See https://stackoverflow.com/questions/4585283/alternate-showing-hiding-window-when-notify-icon-is-clicked +func (s *SystemTray) WindowDebounce(debounce time.Duration) *SystemTray { + s.attachedWindow.Debounce = debounce + return s +} + +func (s *SystemTray) defaultClickHandler() { + if s.attachedWindow.Window == nil { + s.OpenMenu() + return + } + + // Check the initial visibility state + s.attachedWindow.initialClick.Do(func() { + s.attachedWindow.hasBeenShown = s.attachedWindow.Window.IsVisible() + }) + + if runtime.GOOS == "windows" && s.attachedWindow.justClosed { + return + } + + if s.attachedWindow.Window.IsVisible() { + s.attachedWindow.Window.Hide() + } else { + s.attachedWindow.hasBeenShown = true + _ = s.PositionWindow(s.attachedWindow.Window, s.attachedWindow.Offset) + s.attachedWindow.Window.Show().Focus() + } +} + +func (s *SystemTray) OpenMenu() { + if s.menu == nil { + return + } + if s.impl == nil { + return + } + InvokeSync(s.impl.openMenu) +} diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go new file mode 100644 index 000000000..452ce9aba --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.go @@ -0,0 +1,284 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" + +// Show the system tray icon +static void systemTrayShow(void* nsStatusItem) { + dispatch_async(dispatch_get_main_queue(), ^{ + // Get the NSStatusItem + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setVisible:YES]; + }); +} + +// Hide the system tray icon +static void systemTrayHide(void* nsStatusItem) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setVisible:NO]; + }); +} + +*/ +import "C" +import ( + "errors" + "unsafe" + + "github.com/leaanthony/go-ansi-parser" +) + +type macosSystemTray struct { + id uint + label string + icon []byte + menu *Menu + + nsStatusItem unsafe.Pointer + nsImage unsafe.Pointer + nsMenu unsafe.Pointer + iconPosition IconPosition + isTemplateIcon bool + parent *SystemTray + lastClickedScreen unsafe.Pointer +} + +func (s *macosSystemTray) Show() { + if s.nsStatusItem == nil { + return + } + C.systemTrayShow(s.nsStatusItem) +} + +func (s *macosSystemTray) Hide() { + if s.nsStatusItem == nil { + return + } + C.systemTrayHide(s.nsStatusItem) +} + +func (s *macosSystemTray) openMenu() { + if s.nsMenu == nil { + return + } + C.showMenu(s.nsStatusItem, s.nsMenu) +} + +type button int + +const ( + leftButtonDown button = 1 + rightButtonDown button = 3 +) + +// system tray map +var systemTrayMap = make(map[uint]*macosSystemTray) + +//export systrayClickCallback +func systrayClickCallback(id C.long, buttonID C.int) { + // Get the system tray + systemTray := systemTrayMap[uint(id)] + if systemTray == nil { + globalApplication.error("system tray not found: %v", id) + return + } + systemTray.processClick(button(buttonID)) +} + +func (s *macosSystemTray) setIconPosition(position IconPosition) { + s.iconPosition = position +} + +func (s *macosSystemTray) setMenu(menu *Menu) { + s.menu = menu +} + +func (s *macosSystemTray) positionWindow(window *WebviewWindow, offset int) error { + // Get the window's native window + impl := window.impl.(*macosWebviewWindow) + + // Position the window relative to the systray + C.systemTrayPositionWindow(s.nsStatusItem, impl.nsWindow, C.int(offset)) + + return nil +} + +func (s *macosSystemTray) getScreen() (*Screen, error) { + if s.lastClickedScreen != nil { + // Get the screen frame + frame := C.NSScreen_frame(s.lastClickedScreen) + result := &Screen{ + Bounds: Rect{ + X: int(frame.origin.x), + Y: int(frame.origin.y), + Width: int(frame.size.width), + Height: int(frame.size.height), + }, + } + return result, nil + } + return nil, errors.New("no screen available") +} + +func (s *macosSystemTray) bounds() (*Rect, error) { + var rect C.NSRect + var screen unsafe.Pointer + C.systemTrayGetBounds(s.nsStatusItem, &rect, &screen) + + // Store the screen for use in positionWindow + s.lastClickedScreen = screen + + // Return the screen-relative coordinates + result := &Rect{ + X: int(rect.origin.x), + Y: int(rect.origin.y), + Width: int(rect.size.width), + Height: int(rect.size.height), + } + return result, nil +} + +func (s *macosSystemTray) run() { + globalApplication.dispatchOnMainThread(func() { + if s.nsStatusItem != nil { + Fatal("System tray '%d' already running", s.id) + } + s.nsStatusItem = unsafe.Pointer(C.systemTrayNew(C.long(s.id))) + + if s.label != "" { + s.setLabel(s.label) + } + if s.icon != nil { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&s.icon[0]), C.int(len(s.icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + } + if s.menu != nil { + s.menu.Update() + // Convert impl to macosMenu object + s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu + } + }) +} + +func (s *macosSystemTray) setIcon(icon []byte) { + s.icon = icon + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func (s *macosSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *macosSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + globalApplication.dispatchOnMainThread(func() { + s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) + C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) + }) +} + +func (s *macosSystemTray) setTooltip(tooltip string) { + // Tooltips not supported on macOS +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + result := &macosSystemTray{ + parent: s, + id: s.id, + label: s.label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + } + systemTrayMap[s.id] = result + return result +} + +func extractAnsiTextParts(text *ansi.StyledText) (label *C.char, fg *C.char, bg *C.char) { + label = C.CString(text.Label) + if text.FgCol != nil { + fg = C.CString(text.FgCol.Hex) + } + if text.BgCol != nil { + bg = C.CString(text.BgCol.Hex) + } + return +} + +func (s *macosSystemTray) setLabel(label string) { + s.label = label + if !ansi.HasEscapeCodes(label) { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + } else { + parsed, err := ansi.Parse(label) + if err != nil { + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) + return + } + if len(parsed) == 0 { + return + } + label, fg, bg := extractAnsiTextParts(parsed[0]) + var attributedString = C.createAttributedString(label, fg, bg) + if len(parsed) > 1 { + for _, parsedPart := range parsed[1:] { + label, fg, bg = extractAnsiTextParts(parsedPart) + attributedString = C.appendAttributedString(attributedString, label, fg, bg) + } + } + + C.systemTraySetANSILabel(s.nsStatusItem, attributedString) + } +} + +func (s *macosSystemTray) destroy() { + // Remove the status item from the status bar and its associated menu + C.systemTrayDestroy(s.nsStatusItem) +} + +func (s *macosSystemTray) processClick(b button) { + switch b { + case leftButtonDown: + // Check if we have a callback + if s.parent.clickHandler != nil { + s.parent.clickHandler() + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + return + } + if s.menu != nil { + C.showMenu(s.nsStatusItem, s.nsMenu) + } + case rightButtonDown: + // Check if we have a callback + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + return + } + if s.menu != nil { + if s.parent.attachedWindow.Window != nil { + s.parent.attachedWindow.Window.Hide() + } + C.showMenu(s.nsStatusItem, s.nsMenu) + return + } + if s.parent.attachedWindow.Window != nil { + s.parent.defaultClickHandler() + } + } +} diff --git a/v3/pkg/application/systemtray_darwin.h b/v3/pkg/application/systemtray_darwin.h new file mode 100644 index 000000000..bd1940237 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.h @@ -0,0 +1,24 @@ +//go:build darwin + +#include + +@interface StatusItemController : NSObject +@property long id; +- (void)statusItemClicked:(id)sender; +@end + +void* systemTrayNew(long id); +void systemTraySetLabel(void* nsStatusItem, char *label); +void systemTraySetANSILabel(void* nsStatusItem, void* attributedString); +void systemTraySetLabelColor(void* nsStatusItem, char *fg, char *bg); +void* createAttributedString(char *title, char *FG, char *BG); +void* appendAttributedString(void* original, char* label, char* fg, char* bg); +NSImage* imageFromBytes(const unsigned char *bytes, int length); +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate); +void systemTrayDestroy(void* nsStatusItem); +void showMenu(void* nsStatusItem, void *nsMenu); +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect, void **screen); +NSRect NSScreen_frame(void* screen); +void windowSetScreen(void* window, void* screen, int yOffset); +int statusBarHeight(); +void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset); \ No newline at end of file diff --git a/v3/pkg/application/systemtray_darwin.m b/v3/pkg/application/systemtray_darwin.m new file mode 100644 index 000000000..9a6bb2ccc --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.m @@ -0,0 +1,244 @@ +//go:build darwin + +#include "Cocoa/Cocoa.h" +#include "menuitem_darwin.h" +#include "systemtray_darwin.h" + +extern void systrayClickCallback(long, int); + +// StatusItemController.m +@implementation StatusItemController + +- (void)statusItemClicked:(id)sender { + NSEvent *event = [NSApp currentEvent]; + systrayClickCallback(self.id, event.type); +} + +@end + +// Create a new system tray +void* systemTrayNew(long id) { + StatusItemController *controller = [[StatusItemController alloc] init]; + controller.id = id; + NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain]; + [statusItem setTarget:controller]; + [statusItem setAction:@selector(statusItemClicked:)]; + NSButton *button = statusItem.button; + [button sendActionOn:(NSEventMaskLeftMouseDown|NSEventMaskRightMouseDown)]; + return (void*)statusItem; +} + +void systemTraySetLabel(void* nsStatusItem, char *label) { + if( label == NULL ) { + return; + } + // Set the label on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + statusItem.button.title = [NSString stringWithUTF8String:label]; + free(label); + }); +} + +void systemTraySetANSILabel(void* nsStatusItem, void* label) { + if( label == NULL ) { + return; + } + + NSMutableAttributedString* attributedString = (NSMutableAttributedString*) label; + + // Set the label + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem setAttributedTitle:attributedString]; + // [attributedString release]; +} + +void* appendAttributedString(void *currentString, char *title, char *FG, char *BG) { + + NSMutableAttributedString* newString = createAttributedString(title, FG, BG); + if( currentString != NULL ) { + NSMutableAttributedString* current = (NSMutableAttributedString*)currentString; + [current appendAttributedString:newString]; + newString = current; + } + + return (void*)newString; +} + +void* createAttributedString(char *title, char *FG, char *BG) { + + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + // RGBA + if(FG != NULL && strlen(FG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(FG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSForegroundColorAttributeName] = colour; + + } + } + + // Calculate BG colour + if(BG != NULL && strlen(BG) > 0) { + unsigned short r, g, b, a; + + // white by default + r = g = b = a = 255; + int count = sscanf(BG, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a); + if (count > 0) { + NSColor *colour = [NSColor colorWithCalibratedRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]; + dictionary[NSBackgroundColorAttributeName] = colour; + } + } + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:title] attributes:dictionary]; + return (void*)attributedString; +} + +// Create an nsimage from a byte array +NSImage* imageFromBytes(const unsigned char *bytes, int length) { + NSData *data = [NSData dataWithBytes:bytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:data]; + return image; +} + +// Set the icon on the system tray +void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate) { + // Set the icon on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSImage *image = (NSImage *)nsImage; + + NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; + CGFloat thickness = [statusBar thickness]; + [image setSize:NSMakeSize(thickness, thickness)]; + if( isTemplate ) { + [image setTemplate:YES]; + } + statusItem.button.image = [image autorelease]; + statusItem.button.imagePosition = position; + }); +} + +// Destroy system tray +void systemTrayDestroy(void* nsStatusItem) { + // Remove the status item from the status bar and its associated menu + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; + [statusItem release]; + }); +} + +void showMenu(void* nsStatusItem, void *nsMenu) { + // Show the menu on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + [statusItem popUpStatusItemMenu:(NSMenu *)nsMenu]; + // Post a mouse up event so the statusitem defocuses + NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:[NSEvent mouseLocation] + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:nil + eventNumber:0 + clickCount:1 + pressure:1]; + [NSApp postEvent:event atStart:NO]; + [statusItem.button highlight:NO]; + }); +} + +void systemTrayGetBounds(void* nsStatusItem, NSRect *rect, void **outScreen) { + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSStatusBarButton *button = statusItem.button; + + // Get mouse location and find the screen it's on + NSPoint mouseLocation = [NSEvent mouseLocation]; + NSScreen *screen = nil; + NSArray *screens = [NSScreen screens]; + + for (NSScreen *candidate in screens) { + NSRect frame = [candidate frame]; + if (NSPointInRect(mouseLocation, frame)) { + screen = candidate; + break; + } + } + if (!screen) { + screen = [NSScreen mainScreen]; + } + + // Get button frame in screen coordinates + NSRect buttonFrame = button.frame; + NSRect buttonFrameScreen = [button.window convertRectToScreen:buttonFrame]; + + *rect = buttonFrameScreen; + *outScreen = (void*)screen; +} + +NSRect NSScreen_frame(void* screen) { + return [(NSScreen*)screen frame]; +} + +int statusBarHeight() { + NSMenu *mainMenu = [NSApp mainMenu]; + CGFloat menuBarHeight = [mainMenu menuBarHeight]; + return (int)menuBarHeight; +} + +void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset) { + // Get the status item's button + NSStatusBarButton *button = [(NSStatusItem*)nsStatusItem button]; + + // Get the frame in screen coordinates + NSRect frame = [button.window convertRectToScreen:button.frame]; + + // Get the screen that contains the status item + NSScreen *screen = [button.window screen]; + if (screen == nil) { + screen = [NSScreen mainScreen]; + } + + // Get screen's backing scale factor (DPI) + CGFloat scaleFactor = [screen backingScaleFactor]; + + // Get the window's frame + NSRect windowFrame = [(NSWindow*)nsWindow frame]; + + // Calculate the horizontal position (centered under the status item) + CGFloat windowX = frame.origin.x + (frame.size.width - windowFrame.size.width) / 2; + + // If the window would go off the right edge of the screen, adjust it + if (windowX + windowFrame.size.width > screen.frame.origin.x + screen.frame.size.width) { + windowX = screen.frame.origin.x + screen.frame.size.width - windowFrame.size.width; + } + // If the window would go off the left edge of the screen, adjust it + if (windowX < screen.frame.origin.x) { + windowX = screen.frame.origin.x; + } + + // Get screen metrics + NSRect screenFrame = [screen frame]; + NSRect visibleFrame = [screen visibleFrame]; + + // Calculate the vertical position + CGFloat scaledOffset = offset * scaleFactor; + CGFloat windowY = visibleFrame.origin.y + visibleFrame.size.height - windowFrame.size.height - scaledOffset; + + // Set the window's frame + windowFrame.origin.x = windowX; + windowFrame.origin.y = windowY; + [(NSWindow*)nsWindow setFrame:windowFrame display:YES animate:NO]; +} diff --git a/v3/pkg/application/systemtray_linux.go b/v3/pkg/application/systemtray_linux.go new file mode 100644 index 000000000..449753851 --- /dev/null +++ b/v3/pkg/application/systemtray_linux.go @@ -0,0 +1,760 @@ +//go:build linux + +/* +Portions of this code are derived from the project: +- https://github.com/fyne-io/systray +*/ +package application + +import "C" +import ( + "fmt" + "os" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" + "github.com/godbus/dbus/v5/prop" + "github.com/wailsapp/wails/v3/internal/dbus/menu" + "github.com/wailsapp/wails/v3/internal/dbus/notifier" + "github.com/wailsapp/wails/v3/pkg/icons" +) + +const ( + itemPath = "/StatusNotifierItem" + menuPath = "/StatusNotifierMenu" +) + +type linuxSystemTray struct { + parent *SystemTray + + id uint + label string + icon []byte + menu *Menu + + iconPosition IconPosition + isTemplateIcon bool + + quitChan chan struct{} + conn *dbus.Conn + props *prop.Properties + menuProps *prop.Properties + + menuVersion uint32 // need to bump this anytime we change anything + itemMap map[int32]*systrayMenuItem + tooltip string +} + +func (s *linuxSystemTray) getScreen() (*Screen, error) { + _, _, result := getMousePosition() + return result, nil +} + +// dbusMenu is a named struct to map into generated bindings. +// It represents the layout of a menu item +type dbusMenu = struct { + V0 int32 // items' unique id + V1 map[string]dbus.Variant // layout properties + V2 []dbus.Variant // child menu(s) +} + +// systrayMenuItem is an implementation of the menuItemImpl interface +type systrayMenuItem struct { + sysTray *linuxSystemTray + menuItem *MenuItem + dbusItem *dbusMenu +} + +func (s *systrayMenuItem) setBitmap(data []byte) { + s.dbusItem.V1["icon-data"] = dbus.MakeVariant(data) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setTooltip(v string) { + s.dbusItem.V1["tooltip"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setLabel(v string) { + s.dbusItem.V1["label"] = dbus.MakeVariant(v) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) setDisabled(disabled bool) { + v := dbus.MakeVariant(!disabled) + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["enabled"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) destroy() {} + +func (s *systrayMenuItem) setChecked(checked bool) { + v := dbus.MakeVariant(0) + if checked { + v = dbus.MakeVariant(1) + } + if s.dbusItem.V1["toggle-state"] != v { + s.dbusItem.V1["toggle-state"] = v + s.sysTray.update(s) + } +} + +func (s *systrayMenuItem) setAccelerator(accelerator *accelerator) {} +func (s *systrayMenuItem) setHidden(hidden bool) { + s.dbusItem.V1["visible"] = dbus.MakeVariant(!hidden) + s.sysTray.update(s) +} + +func (s *systrayMenuItem) dbus() *dbusMenu { + item := &dbusMenu{ + V0: int32(s.menuItem.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + return item +} + +func (s *linuxSystemTray) setIconPosition(position IconPosition) { + s.iconPosition = position +} + +func (s *linuxSystemTray) processMenu(menu *Menu, parentId int32) { + parentItem, ok := s.itemMap[int32(parentId)] + if !ok { + return + } + parent := parentItem.dbusItem + + for _, item := range menu.items { + menuItem := &dbusMenu{ + V0: int32(item.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + item.impl = &systrayMenuItem{ + sysTray: s, + menuItem: item, + dbusItem: menuItem, + } + s.itemMap[int32(item.id)] = item.impl.(*systrayMenuItem) + + menuItem.V1["enabled"] = dbus.MakeVariant(!item.disabled) + menuItem.V1["visible"] = dbus.MakeVariant(!item.hidden) + if item.label != "" { + menuItem.V1["label"] = dbus.MakeVariant(item.label) + } + if item.bitmap != nil { + menuItem.V1["icon-data"] = dbus.MakeVariant(item.bitmap) + } + switch item.itemType { + case checkbox: + menuItem.V1["toggle-type"] = dbus.MakeVariant("checkmark") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case submenu: + menuItem.V1["children-display"] = dbus.MakeVariant("submenu") + s.processMenu(item.submenu, int32(item.id)) + case text: + case radio: + menuItem.V1["toggle-type"] = dbus.MakeVariant("radio") + v := dbus.MakeVariant(0) + if item.checked { + v = dbus.MakeVariant(1) + } + menuItem.V1["toggle-state"] = v + case separator: + menuItem.V1["type"] = dbus.MakeVariant("separator") + } + + parent.V2 = append(parent.V2, dbus.MakeVariant(menuItem)) + } +} + +func (s *linuxSystemTray) refresh() { + s.menuVersion++ + if err := s.menuProps.Set("com.canonical.dbusmenu", "Version", + dbus.MakeVariant(s.menuVersion)); err != nil { + globalApplication.error("systray error: failed to update menu version: %w", err) + return + } + if err := menu.Emit(s.conn, &menu.Dbusmenu_LayoutUpdatedSignal{ + Path: menuPath, + Body: &menu.Dbusmenu_LayoutUpdatedSignalBody{ + Revision: s.menuVersion, + }, + }); err != nil { + globalApplication.error("systray error: failed to emit layout updated signal: %w", err) + } +} + +func (s *linuxSystemTray) setMenu(menu *Menu) { + if s.parent.attachedWindow.Window != nil { + temp := menu + menu = NewMenu() + title := "Open" + if s.parent.attachedWindow.Window.Name() != "" { + title += " " + s.parent.attachedWindow.Window.Name() + } else { + title += " window" + } + openMenuItem := menu.Add(title) + openMenuItem.OnClick(func(*Context) { + s.parent.clickHandler() + }) + menu.AddSeparator() + menu.Append(temp) + } + s.itemMap = map[int32]*systrayMenuItem{} + // our root menu element + s.itemMap[0] = &systrayMenuItem{ + menuItem: nil, + dbusItem: &dbusMenu{ + V0: int32(0), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + }, + } + menu.processRadioGroups() + s.processMenu(menu, 0) + s.menu = menu +} + +func (s *linuxSystemTray) positionWindow(window *WebviewWindow, offset int) error { + // Get the mouse location on the screen + mouseX, mouseY, currentScreen := getMousePosition() + screenBounds := currentScreen.Size + + // Calculate new X position + newX := mouseX - (window.Width() / 2) + + // Check if the window goes out of the screen bounds on the left side + if newX < 0 { + newX = 0 + } + + // Check if the window goes out of the screen bounds on the right side + if newX+window.Width() > screenBounds.Width { + newX = screenBounds.Width - window.Width() + } + + // Calculate new Y position + newY := mouseY - (window.Height() / 2) + + // Check if the window goes out of the screen bounds on the top + if newY < 0 { + newY = 0 + } + + // Check if the window goes out of the screen bounds on the bottom + if newY+window.Height() > screenBounds.Height { + newY = screenBounds.Height - window.Height() - offset + } + + // Set the new position of the window + window.SetPosition(newX, newY) + return nil +} + +func (s *linuxSystemTray) bounds() (*Rect, error) { + + // Best effort guess at the screen bounds + + return &Rect{}, nil + +} + +func (s *linuxSystemTray) run() { + conn, err := dbus.SessionBus() + if err != nil { + globalApplication.error("systray error: failed to connect to DBus: %w\n", err) + return + } + err = notifier.ExportStatusNotifierItem(conn, itemPath, s) + if err != nil { + globalApplication.error("systray error: failed to export status notifier item: %w\n", err) + } + + err = menu.ExportDbusmenu(conn, menuPath, s) + if err != nil { + globalApplication.error("systray error: failed to export status notifier menu: %w", err) + return + } + + name := fmt.Sprintf("org.kde.StatusNotifierItem-%d-1", os.Getpid()) // register id 1 for this process + _, err = conn.RequestName(name, dbus.NameFlagDoNotQueue) + if err != nil { + globalApplication.error("systray error: failed to request name: %w", err) + // it's not critical error: continue + } + props, err := prop.Export(conn, itemPath, s.createPropSpec()) + if err != nil { + globalApplication.error("systray error: failed to export notifier item properties to bus: %w", err) + return + } + menuProps, err := prop.Export(conn, menuPath, s.createMenuPropSpec()) + if err != nil { + globalApplication.error("systray error: failed to export notifier menu properties to bus: %w", err) + return + } + + s.conn = conn + s.props = props + s.menuProps = menuProps + + node := introspect.Node{ + Name: itemPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + notifier.IntrospectDataStatusNotifierItem, + }, + } + err = conn.Export(introspect.NewIntrospectable(&node), itemPath, "org.freedesktop.DBus.Introspectable") + if err != nil { + globalApplication.error("systray error: failed to export node introspection: %w", err) + return + } + menuNode := introspect.Node{ + Name: menuPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + menu.IntrospectDataDbusmenu, + }, + } + err = conn.Export(introspect.NewIntrospectable(&menuNode), menuPath, + "org.freedesktop.DBus.Introspectable") + if err != nil { + globalApplication.error("systray error: failed to export menu node introspection: %w", err) + return + } + s.setLabel(s.label) + go func() { + defer handlePanic() + s.register() + + if err := conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/DBus"), + dbus.WithMatchInterface("org.freedesktop.DBus"), + dbus.WithMatchSender("org.freedesktop.DBus"), + dbus.WithMatchMember("NameOwnerChanged"), + dbus.WithMatchArg(0, "org.kde.StatusNotifierWatcher"), + ); err != nil { + globalApplication.error("systray error: failed to register signal matching: %w", err) + return + } + + sc := make(chan *dbus.Signal, 10) + conn.Signal(sc) + + for { + select { + case sig := <-sc: + if sig == nil { + return // We get a nil signal when closing the window. + } + // sig.Body has the args, which are [name old_owner new_owner] + if sig.Body[2] != "" { + s.register() + } + + case <-s.quitChan: + return + } + } + }() + + if s.parent.label != "" { + s.setLabel(s.parent.label) + } + + if s.parent.tooltip != "" { + s.setTooltip(s.parent.tooltip) + } + s.setMenu(s.menu) +} + +func (s *linuxSystemTray) setTooltip(_ string) { + // TBD +} + +func (s *linuxSystemTray) setIcon(icon []byte) { + + s.icon = icon + + iconPx, err := iconToPX(icon) + if err != nil { + globalApplication.error("systray error: failed to convert icon to PX: %w", err) + return + } + s.props.SetMust("org.kde.StatusNotifierItem", "IconPixmap", []PX{iconPx}) + + if s.conn == nil { + return + } + + err = notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewIconSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{}, + }) + if err != nil { + globalApplication.error("systray error: failed to emit new icon signal: %w", err) + return + } +} + +func (s *linuxSystemTray) setDarkModeIcon(icon []byte) { + s.setIcon(icon) +} + +func (s *linuxSystemTray) setTemplateIcon(icon []byte) { + s.icon = icon + s.isTemplateIcon = true + s.setIcon(icon) +} + +func newSystemTrayImpl(s *SystemTray) systemTrayImpl { + label := s.label + if label == "" { + label = "Wails" + } + + return &linuxSystemTray{ + parent: s, + id: s.id, + label: label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + quitChan: make(chan struct{}), + menuVersion: 1, + } +} + +func (s *linuxSystemTray) openMenu() { + // FIXME: Emit com.canonical to open? + globalApplication.info("systray error: openMenu not implemented on Linux") +} + +func (s *linuxSystemTray) setLabel(label string) { + s.label = label + + if err := s.props.Set("org.kde.StatusNotifierItem", "Title", dbus.MakeVariant(label)); err != nil { + globalApplication.error("systray error: failed to set Title prop: %w", err) + return + } + + if s.conn == nil { + return + } + + if err := notifier.Emit(s.conn, ¬ifier.StatusNotifierItem_NewTitleSignal{ + Path: itemPath, + Body: ¬ifier.StatusNotifierItem_NewTitleSignalBody{}, + }); err != nil { + globalApplication.error("systray error: failed to emit new title signal: %w", err) + return + } + +} + +func (s *linuxSystemTray) destroy() { + close(s.quitChan) +} + +func (s *linuxSystemTray) createMenuPropSpec() map[string]map[string]*prop.Prop { + return map[string]map[string]*prop.Prop{ + "com.canonical.dbusmenu": { + // update version each time we change something + "Version": { + Value: s.menuVersion, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "TextDirection": { + Value: "ltr", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Status": { + Value: "normal", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: []string{}, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + }, + } +} + +func (s *linuxSystemTray) createPropSpec() map[string]map[string]*prop.Prop { + props := map[string]*prop.Prop{ + "Status": { + Value: "Active", // Passive, Active or NeedsAttention + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Title": { + Value: s.label, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Id": { + Value: s.label, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Category": { + Value: "ApplicationStatus", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconData": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + + "IconName": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "IconThemePath": { + Value: "", + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ItemIsMenu": { + Value: true, + Writable: false, + Emit: prop.EmitTrue, + Callback: nil, + }, + "Menu": { + Value: dbus.ObjectPath(menuPath), + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + "ToolTip": { + Value: tooltip{V2: s.label}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + }, + } + + if s.icon == nil { + // set a basic default one if one isn't set + s.icon = icons.WailsLogoWhiteTransparent + } + if iconPx, err := iconToPX(s.icon); err == nil { + props["IconPixmap"] = &prop.Prop{ + Value: []PX{iconPx}, + Writable: true, + Emit: prop.EmitTrue, + Callback: nil, + } + } + + return map[string]map[string]*prop.Prop{ + "org.kde.StatusNotifierItem": props, + } +} + +func (s *linuxSystemTray) update(i *systrayMenuItem) { + s.itemMap[int32(i.menuItem.id)] = i + s.refresh() +} + +func (s *linuxSystemTray) register() bool { + obj := s.conn.Object("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher") + call := obj.Call("org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem", 0, itemPath) + if call.Err != nil { + globalApplication.error("systray error: failed to register: %w", call.Err) + return false + } + + return true +} + +type PX struct { + W, H int + Pix []byte +} + +func iconToPX(icon []byte) (PX, error) { + img, err := pngToImage(icon) + if err != nil { + return PX{}, err + } + w, h, bytes := ToARGB(img) + return PX{ + W: w, + H: h, + Pix: bytes, + }, nil +} + +// AboutToShow is an implementation of the com.canonical.dbusmenu.AboutToShow method. +func (s *linuxSystemTray) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + return +} + +// AboutToShowGroup is an implementation of the com.canonical.dbusmenu.AboutToShowGroup method. +func (s *linuxSystemTray) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + return +} + +// GetProperty is an implementation of the com.canonical.dbusmenu.GetProperty method. +func (s *linuxSystemTray) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + if item, ok := s.itemMap[id]; ok { + if p, ok := item.dbusItem.V1[name]; ok { + return p, nil + } + } + return +} + +// Event is com.canonical.dbusmenu.Event method. +func (s *linuxSystemTray) Event(id int32, eventID string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + switch eventID { + case "clicked": + if item, ok := s.itemMap[id]; ok { + InvokeAsync(item.menuItem.handleClick) + } + case "opened": + if s.parent.clickHandler != nil { + s.parent.clickHandler() + } + if s.parent.onMenuOpen != nil { + s.parent.onMenuOpen() + } + case "closed": + if s.parent.onMenuClose != nil { + s.parent.onMenuClose() + } + } + return +} + +// EventGroup is an implementation of the com.canonical.dbusmenu.EventGroup method. +func (s *linuxSystemTray) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + for _, event := range events { + fmt.Printf("EventGroup: %v, %v, %v, %v\n", event.V0, event.V1, event.V2, event.V3) + if event.V1 == "clicked" { + item, ok := s.itemMap[event.V0] + if ok { + InvokeAsync(item.menuItem.handleClick) + } + } + } + return +} + +// GetGroupProperties is an implementation of the com.canonical.dbusmenu.GetGroupProperties method. +func (s *linuxSystemTray) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + // FIXME: RLock? + /* instance.menuLock.Lock() + defer instance.menuLock.Unlock() + */ + for _, id := range ids { + if m, ok := s.itemMap[id]; ok { + p := struct { + V0 int32 + V1 map[string]dbus.Variant + }{ + V0: m.dbusItem.V0, + V1: make(map[string]dbus.Variant, len(m.dbusItem.V1)), + } + for k, v := range m.dbusItem.V1 { + p.V1[k] = v + } + properties = append(properties, p) + } + } + return properties, nil +} + +// GetLayout is an implementation of the com.canonical.dbusmenu.GetLayout method. +func (s *linuxSystemTray) GetLayout(parentID int32, recursionDepth int32, propertyNames []string) (revision uint32, layout dbusMenu, err *dbus.Error) { + // FIXME: RLock? + if m, ok := s.itemMap[parentID]; ok { + return s.menuVersion, *m.dbusItem, nil + } + + return +} + +// Activate implements org.kde.StatusNotifierItem.Activate method. +func (s *linuxSystemTray) Activate(x int32, y int32) (err *dbus.Error) { + if s.parent.doubleClickHandler != nil { + s.parent.doubleClickHandler() + } + return +} + +// ContextMenu is org.kde.StatusNotifierItem.ContextMenu method +func (s *linuxSystemTray) ContextMenu(x int32, y int32) (err *dbus.Error) { + fmt.Println("ContextMenu", x, y) + return nil +} + +func (s *linuxSystemTray) Scroll(delta int32, orientation string) (err *dbus.Error) { + fmt.Println("Scroll", delta, orientation) + return +} + +// SecondaryActivate implements org.kde.StatusNotifierItem.SecondaryActivate method. +func (s *linuxSystemTray) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + s.parent.rightClickHandler() + return +} + +// Show is a no-op for Linux +func (s *linuxSystemTray) Show() { + // No-op +} + +// Hide is a no-op for Linux +func (s *linuxSystemTray) Hide() { + // No-op +} + +// tooltip is our data for a tooltip property. +// Param names need to match the generated code... +type tooltip = struct { + V0 string // name + V1 []PX // icons + V2 string // title + V3 string // description +} diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go new file mode 100644 index 000000000..ca30cafce --- /dev/null +++ b/v3/pkg/application/systemtray_windows.go @@ -0,0 +1,488 @@ +//go:build windows + +package application + +import ( + "errors" + "syscall" + "time" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/icons" + + "github.com/samber/lo" + + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +const ( + WM_USER_SYSTRAY = w32.WM_USER + 1 +) + +type windowsSystemTray struct { + parent *SystemTray + + menu *Win32Menu + + // Platform specific implementation + uid uint32 + hwnd w32.HWND + lightModeIcon w32.HICON + darkModeIcon w32.HICON + currentIcon w32.HICON +} + +func (s *windowsSystemTray) openMenu() { + if s.menu == nil { + return + } + // Get the system tray bounds + trayBounds, err := s.bounds() + if err != nil { + return + } + + // Show the menu at the tray bounds + s.menu.ShowAt(trayBounds.X, trayBounds.Y) +} + +func (s *windowsSystemTray) positionWindow(window *WebviewWindow, offset int) error { + // Get the current screen trayBounds + currentScreen, err := s.getScreen() + if err != nil { + return err + } + + screenBounds := currentScreen.WorkArea + windowBounds := window.Bounds() + + newX := screenBounds.Width - windowBounds.Width - offset + newY := screenBounds.Height - windowBounds.Height - offset + + // systray icons in windows can either be in the taskbar + // or in a flyout menu. + var iconIsInTrayBounds bool + iconIsInTrayBounds, err = s.iconIsInTrayBounds() + if err != nil { + return err + } + + var trayBounds *Rect + var centerAlignX, centerAlignY int + + // we only need the traybounds if the icon is in the tray + if iconIsInTrayBounds { + trayBounds, err = s.bounds() + if err != nil { + return err + } + *trayBounds = PhysicalToDipRect(*trayBounds) + centerAlignX = trayBounds.X + (trayBounds.Width / 2) - (windowBounds.Width / 2) + centerAlignY = trayBounds.Y + (trayBounds.Height / 2) - (windowBounds.Height / 2) + } + + taskbarBounds := w32.GetTaskbarPosition() + + // Set the window position based on the icon location + // if the icon is in the taskbar (traybounds) then we need + // to adjust the position so the window is centered on the icon + switch taskbarBounds.UEdge { + case w32.ABE_LEFT: + if iconIsInTrayBounds && centerAlignY <= newY { + newY = centerAlignY + } + newX = screenBounds.X + offset + case w32.ABE_TOP: + if iconIsInTrayBounds && centerAlignX <= newX { + newX = centerAlignX + } + newY = screenBounds.Y + offset + case w32.ABE_RIGHT: + if iconIsInTrayBounds && centerAlignY <= newY { + newY = centerAlignY + } + case w32.ABE_BOTTOM: + if iconIsInTrayBounds && centerAlignX <= newX { + newX = centerAlignX + } + } + newPos := currentScreen.relativeToAbsoluteDipPoint(Point{X: newX, Y: newY}) + windowBounds.X = newPos.X + windowBounds.Y = newPos.Y + window.SetBounds(windowBounds) + return nil +} + +func (s *windowsSystemTray) bounds() (*Rect, error) { + bounds, err := w32.GetSystrayBounds(s.hwnd, s.uid) + if err != nil { + return nil, err + } + + monitor := w32.MonitorFromWindow(s.hwnd, w32.MONITOR_DEFAULTTONEAREST) + if monitor == 0 { + return nil, errors.New("failed to get monitor") + } + + return &Rect{ + X: int(bounds.Left), + Y: int(bounds.Top), + Width: int(bounds.Right - bounds.Left), + Height: int(bounds.Bottom - bounds.Top), + }, nil +} + +func (s *windowsSystemTray) iconIsInTrayBounds() (bool, error) { + bounds, err := w32.GetSystrayBounds(s.hwnd, s.uid) + if err != nil { + return false, err + } + + taskbarRect := w32.GetTaskbarPosition() + + inTasksBar := w32.RectInRect(bounds, &taskbarRect.Rc) + if inTasksBar { + return true, nil + } + + return false, nil +} + +func (s *windowsSystemTray) getScreen() (*Screen, error) { + // Get the screen for this systray + return getScreenForWindowHwnd(s.hwnd) +} + +func (s *windowsSystemTray) setMenu(menu *Menu) { + s.updateMenu(menu) +} + +func (s *windowsSystemTray) run() { + s.hwnd = w32.CreateWindowEx( + 0, + w32.MustStringToUTF16Ptr(globalApplication.options.Windows.WndClass), + nil, + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + 0, + nil) + if s.hwnd == 0 { + panic(syscall.GetLastError()) + } + + nid := w32.NOTIFYICONDATA{ + HWnd: s.hwnd, + UID: uint32(s.parent.id), + UFlags: w32.NIF_ICON | w32.NIF_MESSAGE, + HIcon: s.currentIcon, + UCallbackMessage: WM_USER_SYSTRAY, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + + for retries := range 6 { + if !w32.ShellNotifyIcon(w32.NIM_ADD, &nid) { + if retries == 5 { + globalApplication.fatal("failed to register system tray icon: %w", syscall.GetLastError()) + } + + time.Sleep(500 * time.Millisecond) + continue + } + break + } + + nid.UVersion = w32.NOTIFYICON_VERSION + + if !w32.ShellNotifyIcon(w32.NIM_SETVERSION, &nid) { + panic(syscall.GetLastError()) + } + + // Get the application icon if available + defaultIcon := w32.LoadIconWithResourceID(w32.GetModuleHandle(""), w32.RT_ICON) + if defaultIcon != 0 { + s.lightModeIcon = defaultIcon + s.darkModeIcon = defaultIcon + } else { + s.lightModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayLight)) + s.darkModeIcon = lo.Must(w32.CreateSmallHIconFromImage(icons.SystrayDark)) + } + + // Use custom icons if provided + if s.parent.icon != nil { + // Create a new icon and destroy the old one + newIcon := lo.Must(w32.CreateSmallHIconFromImage(s.parent.icon)) + if s.lightModeIcon != 0 && s.lightModeIcon != defaultIcon { + w32.DestroyIcon(s.lightModeIcon) + } + s.lightModeIcon = newIcon + } + if s.parent.darkModeIcon != nil { + // Create a new icon and destroy the old one + newIcon := lo.Must(w32.CreateSmallHIconFromImage(s.parent.darkModeIcon)) + if s.darkModeIcon != 0 && s.darkModeIcon != defaultIcon && s.darkModeIcon != s.lightModeIcon { + w32.DestroyIcon(s.darkModeIcon) + } + s.darkModeIcon = newIcon + } + s.uid = nid.UID + + if s.parent.menu != nil { + s.updateMenu(s.parent.menu) + } + + if s.parent.tooltip != "" { + s.setTooltip(s.parent.tooltip) + } + + // Set Default Callbacks + if s.parent.clickHandler == nil { + s.parent.clickHandler = func() { + globalApplication.debug("Left Button Clicked") + } + } + if s.parent.rightClickHandler == nil { + s.parent.rightClickHandler = func() { + if s.menu != nil { + s.openMenu() + } + } + } + + // Update the icon + s.updateIcon() + + // Listen for dark mode changes + globalApplication.Event.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *ApplicationEvent) { + s.updateIcon() + }) + + // Register the system tray + getNativeApplication().registerSystemTray(s) +} + +func (s *windowsSystemTray) updateIcon() { + var newIcon w32.HICON + if w32.IsCurrentlyDarkMode() { + newIcon = s.darkModeIcon + } else { + newIcon = s.lightModeIcon + } + if s.currentIcon == newIcon { + return + } + + // Store the old icon to destroy it after updating + oldIcon := s.currentIcon + + s.currentIcon = newIcon + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_ICON + if s.currentIcon != 0 { + nid.HIcon = s.currentIcon + } + + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + panic(syscall.GetLastError()) + } + + // Destroy the old icon handle if it exists and is not one of our default icons + if oldIcon != 0 && oldIcon != s.lightModeIcon && oldIcon != s.darkModeIcon { + w32.DestroyIcon(oldIcon) + } +} + +func (s *windowsSystemTray) newNotifyIconData() w32.NOTIFYICONDATA { + nid := w32.NOTIFYICONDATA{ + UID: s.uid, + HWnd: s.hwnd, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + return nid +} + +func (s *windowsSystemTray) setIcon(icon []byte) { + var err error + // Destroy the previous light mode icon if it exists + if s.lightModeIcon != 0 { + w32.DestroyIcon(s.lightModeIcon) + } + s.lightModeIcon, err = w32.CreateSmallHIconFromImage(icon) + if err != nil { + panic(syscall.GetLastError()) + } + if s.darkModeIcon == 0 { + s.darkModeIcon = s.lightModeIcon + } + // Update the icon + s.updateIcon() +} + +func (s *windowsSystemTray) setDarkModeIcon(icon []byte) { + var err error + // Destroy the previous dark mode icon if it exists + if s.darkModeIcon != 0 { + w32.DestroyIcon(s.darkModeIcon) + } + s.darkModeIcon, err = w32.CreateSmallHIconFromImage(icon) + if err != nil { + panic(syscall.GetLastError()) + } + if s.lightModeIcon == 0 { + s.lightModeIcon = s.darkModeIcon + } + // Update the icon + s.updateIcon() +} + +func newSystemTrayImpl(parent *SystemTray) systemTrayImpl { + return &windowsSystemTray{ + parent: parent, + } +} + +func (s *windowsSystemTray) wndProc(msg uint32, wParam, lParam uintptr) uintptr { + switch msg { + case WM_USER_SYSTRAY: + msg := lParam & 0xffff + switch msg { + case w32.WM_LBUTTONUP: + if s.parent.clickHandler != nil { + s.parent.clickHandler() + } + case w32.WM_RBUTTONUP: + if s.parent.rightClickHandler != nil { + s.parent.rightClickHandler() + } + case w32.WM_LBUTTONDBLCLK: + if s.parent.doubleClickHandler != nil { + s.parent.doubleClickHandler() + } + case w32.WM_RBUTTONDBLCLK: + if s.parent.rightDoubleClickHandler != nil { + s.parent.rightDoubleClickHandler() + } + case 0x0406: + if s.parent.mouseEnterHandler != nil { + s.parent.mouseEnterHandler() + } + case 0x0407: + if s.parent.mouseLeaveHandler != nil { + s.parent.mouseLeaveHandler() + } + } + // println(w32.WMMessageToString(msg)) + + // Menu processing + case w32.WM_COMMAND: + cmdMsgID := int(wParam & 0xffff) + switch cmdMsgID { + default: + s.menu.ProcessCommand(cmdMsgID) + } + default: + // msg := int(wParam & 0xffff) + // println(w32.WMMessageToString(uintptr(msg))) + } + + return w32.DefWindowProc(s.hwnd, msg, wParam, lParam) +} + +func (s *windowsSystemTray) updateMenu(menu *Menu) { + s.menu = NewPopupMenu(s.hwnd, menu) + s.menu.onMenuOpen = s.parent.onMenuOpen + s.menu.onMenuClose = s.parent.onMenuClose + s.menu.Update() +} + +// Based on the idea from https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304 +func (s *windowsSystemTray) setTooltip(tooltip string) { + // Ensure the tooltip length is within the limit (64 characters for szTip) + if len(tooltip) > 64 { + tooltip = tooltip[:64] + } + + // Create a new NOTIFYICONDATA structure + nid := s.newNotifyIconData() + nid.UFlags = w32.NIF_TIP + tooltipUTF16, err := w32.StringToUTF16(tooltip) + if err != nil { + return + } + + copy(nid.SzTip[:], tooltipUTF16) + + // Modify the tray icon with the new tooltip + if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { + return + } + nid.UVersion = 3 // Version 4 does not suport + if !w32.ShellNotifyIcon(w32.NIM_SETVERSION, &nid) { + return + } +} + +// ---- Unsupported ---- +func (s *windowsSystemTray) setLabel(label string) {} + +func (s *windowsSystemTray) setTemplateIcon(_ []byte) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) setIconPosition(position IconPosition) { + // Unsupported - do nothing +} + +func (s *windowsSystemTray) destroy() { + // Remove and delete the system tray + getNativeApplication().unregisterSystemTray(s) + if s.menu != nil { + s.menu.Destroy() + } + w32.DestroyWindow(s.hwnd) + // destroy the notification icon + nid := s.newNotifyIconData() + if !w32.ShellNotifyIcon(w32.NIM_DELETE, &nid) { + globalApplication.debug(syscall.GetLastError().Error()) + } + + // Clean up icon handles + if s.lightModeIcon != 0 { + w32.DestroyIcon(s.lightModeIcon) + s.lightModeIcon = 0 + } + if s.darkModeIcon != 0 && s.darkModeIcon != s.lightModeIcon { + w32.DestroyIcon(s.darkModeIcon) + s.darkModeIcon = 0 + } + s.currentIcon = 0 +} + +func (s *windowsSystemTray) Show() { + // No-op +} + +func (s *windowsSystemTray) Hide() { + // No-op +} + +func (s *windowsSystemTray) reshow() { + // Add icons back to systray + nid := w32.NOTIFYICONDATA{ + HWnd: s.hwnd, + UID: uint32(s.parent.id), + UFlags: w32.NIF_ICON | w32.NIF_MESSAGE, + HIcon: s.currentIcon, + UCallbackMessage: WM_USER_SYSTRAY, + } + nid.CbSize = uint32(unsafe.Sizeof(nid)) + // Show the icon + if !w32.ShellNotifyIcon(w32.NIM_ADD, &nid) { + panic(syscall.GetLastError()) + } +} diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go new file mode 100644 index 000000000..67d125074 --- /dev/null +++ b/v3/pkg/application/webview_window.go @@ -0,0 +1,1430 @@ +package application + +import ( + "errors" + "fmt" + "runtime" + "slices" + "strings" + "sync" + "sync/atomic" + "text/template" + + "github.com/leaanthony/u" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Enabled means the feature should be enabled +var Enabled = u.True + +// Disabled means the feature should be disabled +var Disabled = u.False + +// LRTB is a struct that holds Left, Right, Top, Bottom values +type LRTB struct { + Left int + Right int + Top int + Bottom int +} + +type ( + webviewWindowImpl interface { + setTitle(title string) + setSize(width, height int) + setAlwaysOnTop(alwaysOnTop bool) + setURL(url string) + setResizable(resizable bool) + setMinSize(width, height int) + setMaxSize(width, height int) + execJS(js string) + setBackgroundColour(color RGBA) + run() + center() + size() (int, int) + width() int + height() int + destroy() + reload() + forceReload() + openDevTools() + zoomReset() + zoomIn() + zoomOut() + getZoom() float64 + setZoom(zoom float64) + close() + zoom() + setHTML(html string) + on(eventID uint) + minimise() + unminimise() + maximise() + unmaximise() + fullscreen() + unfullscreen() + isMinimised() bool + isMaximised() bool + isFullscreen() bool + isNormal() bool + isVisible() bool + isFocused() bool + focus() + show() + hide() + getScreen() (*Screen, error) + setFrameless(bool) + openContextMenu(menu *Menu, data *ContextMenuData) + nativeWindowHandle() uintptr + startDrag() error + startResize(border string) error + print() error + setEnabled(enabled bool) + physicalBounds() Rect + setPhysicalBounds(physicalBounds Rect) + bounds() Rect + setBounds(bounds Rect) + position() (int, int) + setPosition(x int, y int) + relativePosition() (int, int) + setRelativePosition(x int, y int) + flash(enabled bool) + handleKeyEvent(acceleratorString string) + getBorderSizes() *LRTB + setMinimiseButtonState(state ButtonState) + setMaximiseButtonState(state ButtonState) + setCloseButtonState(state ButtonState) + isIgnoreMouseEvents() bool + setIgnoreMouseEvents(ignore bool) + cut() + copy() + paste() + undo() + delete() + selectAll() + redo() + showMenuBar() + hideMenuBar() + toggleMenuBar() + setMenu(menu *Menu) + } +) + +type WindowEvent struct { + ctx *WindowEventContext + Cancelled bool +} + +func (w *WindowEvent) Context() *WindowEventContext { + return w.ctx +} + +func NewWindowEvent() *WindowEvent { + return &WindowEvent{} +} + +func (w *WindowEvent) Cancel() { + w.Cancelled = true +} + +type WindowEventListener struct { + callback func(event *WindowEvent) +} + +type WebviewWindow struct { + options WebviewWindowOptions + impl webviewWindowImpl + id uint + + eventListeners map[uint][]*WindowEventListener + eventListenersLock sync.RWMutex + eventHooks map[uint][]*WindowEventListener + eventHooksLock sync.RWMutex + + // A map of listener cancellation functions + cancellersLock sync.RWMutex + cancellers []func() + + // keyBindings holds the keybindings for the window + keyBindings map[string]func(*WebviewWindow) + keyBindingsLock sync.RWMutex + + // menuBindings holds the menu bindings for the window + menuBindings map[string]*MenuItem + menuBindingsLock sync.RWMutex + + // Indicates that the window is destroyed + destroyed bool + destroyedLock sync.RWMutex + + // Flags for managing the runtime + // runtimeLoaded indicates that the runtime has been loaded + runtimeLoaded bool + // pendingJS holds JS that was sent to the window before the runtime was loaded + pendingJS []string + + // unconditionallyClose marks the window to be unconditionally closed (atomic) + unconditionallyClose uint32 +} + +func (w *WebviewWindow) SetMenu(menu *Menu) { + switch runtime.GOOS { + case "darwin": + return + case "windows": + w.options.Windows.Menu = menu + case "linux": + w.options.Linux.Menu = menu + } + if w.impl != nil { + InvokeSync(func() { + w.impl.setMenu(menu) + }) + } +} + +// EmitEvent emits an event from the window +func (w *WebviewWindow) EmitEvent(name string, data ...any) { + globalApplication.Event.EmitEvent(&CustomEvent{ + Name: name, + Data: data, + Sender: w.Name(), + }) +} + +var windowID uint +var windowIDLock sync.RWMutex + +func getWindowID() uint { + windowIDLock.Lock() + defer windowIDLock.Unlock() + windowID++ + return windowID +} + +// FIXME: This should like be an interface method (TDM) +// Use onApplicationEvent to register a callback for an application event from a window. +// This will handle tidying up the callback when the window is destroyed +func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func(*ApplicationEvent)) { + cancelFn := globalApplication.Event.OnApplicationEvent(eventType, callback) + w.addCancellationFunction(cancelFn) +} + +func (w *WebviewWindow) markAsDestroyed() { + w.destroyedLock.Lock() + defer w.destroyedLock.Unlock() + w.destroyed = true +} + +func (w *WebviewWindow) setupEventMapping() { + + var mapping map[events.WindowEventType]events.WindowEventType + switch runtime.GOOS { + case "darwin": + mapping = w.options.Mac.EventMapping + case "windows": + mapping = w.options.Windows.EventMapping + case "linux": + // TBD + } + if mapping == nil { + mapping = events.DefaultWindowEventMapping() + } + + for source, target := range mapping { + source := source + target := target + w.OnWindowEvent(source, func(event *WindowEvent) { + w.emit(target) + }) + } +} + +// NewWindow creates a new window with the given options +func NewWindow(options WebviewWindowOptions) *WebviewWindow { + + thisWindowID := getWindowID() + + if options.Width == 0 { + options.Width = 800 + } + if options.Height == 0 { + options.Height = 600 + } + if options.URL == "" { + options.URL = "/" + } + + if options.Name == "" { + options.Name = fmt.Sprintf("window-%d", thisWindowID) + } + + result := &WebviewWindow{ + id: thisWindowID, + options: options, + eventListeners: make(map[uint][]*WindowEventListener), + eventHooks: make(map[uint][]*WindowEventListener), + menuBindings: make(map[string]*MenuItem), + } + + result.setupEventMapping() + + // Listen for window closing events and de + result.OnWindowEvent(events.Common.WindowClosing, func(event *WindowEvent) { + atomic.StoreUint32(&result.unconditionallyClose, 1) + InvokeSync(result.markAsDestroyed) + InvokeSync(result.impl.close) + globalApplication.Window.Remove(result.id) + }) + + // Process keybindings + if result.options.KeyBindings != nil { + result.keyBindings = processKeyBindingOptions(result.options.KeyBindings) + } + + return result +} + +func processKeyBindingOptions( + keyBindings map[string]func(window *WebviewWindow), +) map[string]func(window *WebviewWindow) { + result := make(map[string]func(window *WebviewWindow)) + for key, callback := range keyBindings { + // Parse the key to an accelerator + acc, err := parseAccelerator(key) + if err != nil { + globalApplication.error("invalid keybinding: %w", err) + continue + } + result[acc.String()] = callback + globalApplication.debug("Added Keybinding", "accelerator", acc.String()) + } + return result +} + +func (w *WebviewWindow) addCancellationFunction(canceller func()) { + w.cancellersLock.Lock() + defer w.cancellersLock.Unlock() + w.cancellers = append(w.cancellers, canceller) +} + +func (w *WebviewWindow) CallError(callID string, result string, isJSON bool) { + if w.impl != nil { + w.impl.execJS( + fmt.Sprintf( + "_wails.callErrorHandler('%s', '%s', %t);", + callID, + template.JSEscapeString(result), + isJSON, + ), + ) + } +} + +func (w *WebviewWindow) CallResponse(callID string, result string) { + if w.impl != nil { + w.impl.execJS( + fmt.Sprintf( + "_wails.callResultHandler('%s', '%s', true);", + callID, + template.JSEscapeString(result), + ), + ) + } +} + +func (w *WebviewWindow) DialogError(dialogID string, result string) { + if w.impl != nil { + w.impl.execJS( + fmt.Sprintf( + "_wails.dialogErrorCallback('%s', '%s');", + dialogID, + template.JSEscapeString(result), + ), + ) + } +} + +func (w *WebviewWindow) DialogResponse(dialogID string, result string, isJSON bool) { + if w.impl != nil { + w.impl.execJS( + fmt.Sprintf( + "_wails.dialogResultCallback('%s', '%s', %t);", + dialogID, + template.JSEscapeString(result), + isJSON, + ), + ) + } +} + +func (w *WebviewWindow) ID() uint { + return w.id +} + +// SetTitle sets the title of the window +func (w *WebviewWindow) SetTitle(title string) Window { + w.options.Title = title + if w.impl != nil { + InvokeSync(func() { + w.impl.setTitle(title) + }) + } + return w +} + +// Name returns the name of the window +func (w *WebviewWindow) Name() string { + return w.options.Name +} + +// SetSize sets the size of the window +func (w *WebviewWindow) SetSize(width, height int) Window { + // Don't set size if fullscreen + if w.IsFullscreen() { + return w + } + w.options.Width = width + w.options.Height = height + + var newMaxWidth = w.options.MaxWidth + var newMaxHeight = w.options.MaxHeight + if width > w.options.MaxWidth && w.options.MaxWidth != 0 { + newMaxWidth = width + } + if height > w.options.MaxHeight && w.options.MaxHeight != 0 { + newMaxHeight = height + } + + if newMaxWidth != 0 || newMaxHeight != 0 { + w.SetMaxSize(newMaxWidth, newMaxHeight) + } + + var newMinWidth = w.options.MinWidth + var newMinHeight = w.options.MinHeight + if width < w.options.MinWidth && w.options.MinWidth != 0 { + newMinWidth = width + } + if height < w.options.MinHeight && w.options.MinHeight != 0 { + newMinHeight = height + } + + if newMinWidth != 0 || newMinHeight != 0 { + w.SetMinSize(newMinWidth, newMinHeight) + } + + if w.impl != nil { + InvokeSync(func() { + w.impl.setSize(width, height) + }) + } + return w +} + +func (w *WebviewWindow) Run() { + if w.impl != nil { + return + } + w.impl = newWindowImpl(w) + + InvokeSync(w.impl.run) +} + +// SetAlwaysOnTop sets the window to be always on top. +func (w *WebviewWindow) SetAlwaysOnTop(b bool) Window { + w.options.AlwaysOnTop = b + if w.impl != nil { + InvokeSync(func() { + w.impl.setAlwaysOnTop(b) + }) + } + return w +} + +// Show shows the window. +func (w *WebviewWindow) Show() Window { + if globalApplication.impl == nil { + return w + } + if w.impl == nil || w.isDestroyed() { + InvokeSync(w.Run) + return w + } + w.options.Hidden = false + InvokeSync(w.impl.show) + return w +} + +// Hide hides the window. +func (w *WebviewWindow) Hide() Window { + w.options.Hidden = true + if w.impl != nil { + InvokeSync(w.impl.hide) + } + return w +} + +func (w *WebviewWindow) SetURL(s string) Window { + url, _ := assetserver.GetStartURL(s) + w.options.URL = url + if w.impl != nil { + InvokeSync(func() { + w.impl.setURL(url) + }) + } + return w +} + +func (w *WebviewWindow) GetBorderSizes() *LRTB { + if w.impl != nil { + return InvokeSyncWithResult(w.impl.getBorderSizes) + } + return &LRTB{} +} + +// SetZoom sets the zoom level of the window. +func (w *WebviewWindow) SetZoom(magnification float64) Window { + w.options.Zoom = magnification + if w.impl != nil { + InvokeSync(func() { + w.impl.setZoom(magnification) + }) + } + return w +} + +// GetZoom returns the current zoom level of the window. +func (w *WebviewWindow) GetZoom() float64 { + if w.impl != nil { + return InvokeSyncWithResult(w.impl.getZoom) + } + return 1 +} + +// SetResizable sets whether the window is resizable. +func (w *WebviewWindow) SetResizable(b bool) Window { + w.options.DisableResize = !b + if w.impl != nil { + InvokeSync(func() { + w.impl.setResizable(b) + }) + } + return w +} + +// Resizable returns true if the window is resizable. +func (w *WebviewWindow) Resizable() bool { + return !w.options.DisableResize +} + +// SetMinSize sets the minimum size of the window. +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) Window { + w.options.MinWidth = minWidth + w.options.MinHeight = minHeight + + currentWidth, currentHeight := w.Size() + newWidth, newHeight := currentWidth, currentHeight + + var newSize bool + if minHeight != 0 && currentHeight < minHeight { + newHeight = minHeight + w.options.Height = newHeight + newSize = true + } + if minWidth != 0 && currentWidth < minWidth { + newWidth = minWidth + w.options.Width = newWidth + newSize = true + } + if w.impl != nil { + if newSize { + InvokeSync(func() { + w.impl.setSize(newWidth, newHeight) + }) + } + InvokeSync(func() { + w.impl.setMinSize(minWidth, minHeight) + }) + } + return w +} + +// SetMaxSize sets the maximum size of the window. +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { + w.options.MaxWidth = maxWidth + w.options.MaxHeight = maxHeight + + currentWidth, currentHeight := w.Size() + newWidth, newHeight := currentWidth, currentHeight + + var newSize bool + if maxHeight != 0 && currentHeight > maxHeight { + newHeight = maxHeight + w.options.Height = maxHeight + newSize = true + } + if maxWidth != 0 && currentWidth > maxWidth { + newWidth = maxWidth + w.options.Width = maxWidth + newSize = true + } + if w.impl != nil { + if newSize { + InvokeSync(func() { + w.impl.setSize(newWidth, newHeight) + }) + } + InvokeSync(func() { + w.impl.setMaxSize(maxWidth, maxHeight) + }) + } + return w +} + +// ExecJS executes the given javascript in the context of the window. +func (w *WebviewWindow) ExecJS(js string) { + if w.impl == nil || w.isDestroyed() { + return + } + if w.runtimeLoaded { + InvokeSync(func() { + w.impl.execJS(js) + }) + } else { + w.pendingJS = append(w.pendingJS, js) + } +} + +// Fullscreen sets the window to fullscreen mode. Min/Max size constraints are disabled. +func (w *WebviewWindow) Fullscreen() Window { + if w.impl == nil || w.isDestroyed() { + w.options.StartState = WindowStateFullscreen + return w + } + if !w.IsFullscreen() { + w.DisableSizeConstraints() + InvokeSync(w.impl.fullscreen) + } + return w +} + +func (w *WebviewWindow) SetMinimiseButtonState(state ButtonState) Window { + w.options.MinimiseButtonState = state + if w.impl != nil { + InvokeSync(func() { + w.impl.setMinimiseButtonState(state) + }) + } + return w +} + +func (w *WebviewWindow) SetMaximiseButtonState(state ButtonState) Window { + w.options.MaximiseButtonState = state + if w.impl != nil { + InvokeSync(func() { + w.impl.setMaximiseButtonState(state) + }) + } + return w +} + +func (w *WebviewWindow) SetCloseButtonState(state ButtonState) Window { + w.options.CloseButtonState = state + if w.impl != nil { + InvokeSync(func() { + w.impl.setCloseButtonState(state) + }) + } + return w +} + +// Flash flashes the window's taskbar button/icon. +// Useful to indicate that attention is required. Windows only. +func (w *WebviewWindow) Flash(enabled bool) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.flash(enabled) + }) +} + +// IsMinimised returns true if the window is minimised +func (w *WebviewWindow) IsMinimised() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isMinimised) +} + +// IsVisible returns true if the window is visible +func (w *WebviewWindow) IsVisible() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isVisible) +} + +// IsMaximised returns true if the window is maximised +func (w *WebviewWindow) IsMaximised() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isMaximised) +} + +// Size returns the size of the window +func (w *WebviewWindow) Size() (int, int) { + if w.impl == nil || w.isDestroyed() { + return 0, 0 + } + var width, height int + InvokeSync(func() { + width, height = w.impl.size() + }) + return width, height +} + +// IsFocused returns true if the window is currently focused +func (w *WebviewWindow) IsFocused() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isFocused) +} + +// IsFullscreen returns true if the window is fullscreen +func (w *WebviewWindow) IsFullscreen() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isFullscreen) +} + +// SetBackgroundColour sets the background colour of the window +func (w *WebviewWindow) SetBackgroundColour(colour RGBA) Window { + w.options.BackgroundColour = colour + if w.impl != nil { + InvokeSync(func() { + w.impl.setBackgroundColour(colour) + }) + } + return w +} + +func (w *WebviewWindow) HandleMessage(message string) { + // Check for special messages + switch true { + case message == "wails:drag": + if !w.IsFullscreen() { + InvokeSync(func() { + err := w.startDrag() + if err != nil { + w.Error("failed to start drag: %w", err) + } + }) + } + case strings.HasPrefix(message, "wails:resize:"): + if !w.IsFullscreen() { + sl := strings.Split(message, ":") + if len(sl) != 3 { + w.Error("unknown message returned from dispatcher: %s", message) + return + } + err := w.startResize(sl[2]) + if err != nil { + w.Error("%w", err) + } + } + case message == "wails:runtime:ready": + w.emit(events.Common.WindowRuntimeReady) + w.runtimeLoaded = true + w.SetResizable(!w.options.DisableResize) + for _, js := range w.pendingJS { + w.ExecJS(js) + } + default: + w.Error("unknown message sent via 'invoke' on frontend: %v", message) + } +} + +func (w *WebviewWindow) startResize(border string) error { + if w.impl == nil || w.isDestroyed() { + return nil + } + return InvokeSyncWithResult(func() error { + return w.impl.startResize(border) + }) +} + +// Center centers the window on the screen +func (w *WebviewWindow) Center() { + if w.impl == nil || w.isDestroyed() { + w.options.InitialPosition = WindowCentered + return + } + InvokeSync(w.impl.center) +} + +// OnWindowEvent registers a callback for the given window event +func (w *WebviewWindow) OnWindowEvent( + eventType events.WindowEventType, + callback func(event *WindowEvent), +) func() { + eventID := uint(eventType) + windowEventListener := &WindowEventListener{ + callback: callback, + } + w.eventListenersLock.Lock() + w.eventListeners[eventID] = append(w.eventListeners[eventID], windowEventListener) + w.eventListenersLock.Unlock() + if w.impl != nil { + w.impl.on(eventID) + } + + return func() { + // Check if eventListener is already locked + w.eventListenersLock.Lock() + w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener) + w.eventListenersLock.Unlock() + } +} + +// RegisterHook registers a hook for the given window event +func (w *WebviewWindow) RegisterHook( + eventType events.WindowEventType, + callback func(event *WindowEvent), +) func() { + eventID := uint(eventType) + w.eventHooksLock.Lock() + defer w.eventHooksLock.Unlock() + windowEventHook := &WindowEventListener{ + callback: callback, + } + w.eventHooks[eventID] = append(w.eventHooks[eventID], windowEventHook) + + return func() { + w.eventHooksLock.Lock() + defer w.eventHooksLock.Unlock() + w.eventHooks[eventID] = lo.Without(w.eventHooks[eventID], windowEventHook) + } +} + +func (w *WebviewWindow) HandleWindowEvent(id uint) { + // Get hooks + w.eventHooksLock.RLock() + hooks := w.eventHooks[id] + w.eventHooksLock.RUnlock() + + // Create new WindowEvent + thisEvent := NewWindowEvent() + + for _, thisHook := range hooks { + thisHook.callback(thisEvent) + if thisEvent.Cancelled { + return + } + } + + // Copy the w.eventListeners + w.eventListenersLock.RLock() + var tempListeners = slices.Clone(w.eventListeners[id]) + w.eventListenersLock.RUnlock() + + for _, listener := range tempListeners { + go func() { + defer handlePanic() + listener.callback(thisEvent) + }() + } + w.dispatchWindowEvent(id) +} + +// Width returns the width of the window +func (w *WebviewWindow) Width() int { + if w.impl == nil || w.isDestroyed() { + return 0 + } + return InvokeSyncWithResult(w.impl.width) +} + +// Height returns the height of the window +func (w *WebviewWindow) Height() int { + if w.impl == nil || w.isDestroyed() { + return 0 + } + return InvokeSyncWithResult(w.impl.height) +} + +// PhysicalBounds returns the physical bounds of the window +func (w *WebviewWindow) PhysicalBounds() Rect { + if w.impl == nil || w.isDestroyed() { + return Rect{} + } + var rect Rect + InvokeSync(func() { + rect = w.impl.physicalBounds() + }) + return rect +} + +// SetPhysicalBounds sets the physical bounds of the window +func (w *WebviewWindow) SetPhysicalBounds(physicalBounds Rect) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.setPhysicalBounds(physicalBounds) + }) +} + +// Bounds returns the DIP bounds of the window +func (w *WebviewWindow) Bounds() Rect { + if w.impl == nil || w.isDestroyed() { + return Rect{} + } + var rect Rect + InvokeSync(func() { + rect = w.impl.bounds() + }) + return rect +} + +// SetBounds sets the DIP bounds of the window +func (w *WebviewWindow) SetBounds(bounds Rect) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.setBounds(bounds) + }) +} + +// Position returns the absolute position of the window +func (w *WebviewWindow) Position() (int, int) { + if w.impl == nil || w.isDestroyed() { + return 0, 0 + } + var x, y int + InvokeSync(func() { + x, y = w.impl.position() + }) + return x, y +} + +// SetPosition sets the absolute position of the window. +func (w *WebviewWindow) SetPosition(x int, y int) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.setPosition(x, y) + }) +} + +// RelativePosition returns the position of the window relative to the screen WorkArea on which it is +func (w *WebviewWindow) RelativePosition() (int, int) { + if w.impl == nil || w.isDestroyed() { + return 0, 0 + } + var x, y int + InvokeSync(func() { + x, y = w.impl.relativePosition() + }) + return x, y +} + +// SetRelativePosition sets the position of the window relative to the screen WorkArea on which it is. +func (w *WebviewWindow) SetRelativePosition(x, y int) Window { + w.options.X = x + w.options.Y = y + if w.impl != nil { + InvokeSync(func() { + w.impl.setRelativePosition(x, y) + }) + } + return w +} + +func (w *WebviewWindow) destroy() { + if w.impl == nil || w.isDestroyed() { + return + } + + // Cancel the callbacks + for _, cancelFunc := range w.cancellers { + cancelFunc() + } + + InvokeSync(w.impl.destroy) +} + +// Reload reloads the page assets +func (w *WebviewWindow) Reload() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.reload) +} + +// ForceReload forces the window to reload the page assets +func (w *WebviewWindow) ForceReload() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.forceReload) +} + +// ToggleFullscreen toggles the window between fullscreen and normal +func (w *WebviewWindow) ToggleFullscreen() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + if w.IsFullscreen() { + w.UnFullscreen() + } else { + w.Fullscreen() + } + }) +} + +// ToggleMaximise toggles the window between maximised and normal +func (w *WebviewWindow) ToggleMaximise() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + if w.IsMaximised() { + w.UnMaximise() + } else { + w.Maximise() + } + }) +} + +// ToggleFrameless toggles the window between frameless and normal +func (w *WebviewWindow) ToggleFrameless() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.SetFrameless(!w.options.Frameless) + }) +} + +func (w *WebviewWindow) OpenDevTools() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.openDevTools) +} + +// ZoomReset resets the zoom level of the webview content to 100% +func (w *WebviewWindow) ZoomReset() Window { + if w.impl != nil { + InvokeSync(w.impl.zoomReset) + } + return w +} + +// ZoomIn increases the zoom level of the webview content +func (w *WebviewWindow) ZoomIn() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.zoomIn) +} + +// ZoomOut decreases the zoom level of the webview content +func (w *WebviewWindow) ZoomOut() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.zoomOut) +} + +// Close closes the window +func (w *WebviewWindow) Close() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.emit(events.Common.WindowClosing) + }) +} + +func (w *WebviewWindow) Zoom() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.zoom) +} + +// SetHTML sets the HTML of the window to the given html string. +func (w *WebviewWindow) SetHTML(html string) Window { + w.options.HTML = html + if w.impl != nil { + InvokeSync(func() { + w.impl.setHTML(html) + }) + } + return w +} + +// Minimise minimises the window. +func (w *WebviewWindow) Minimise() Window { + if w.impl == nil || w.isDestroyed() { + w.options.StartState = WindowStateMinimised + return w + } + if !w.IsMinimised() { + InvokeSync(w.impl.minimise) + } + return w +} + +// Maximise maximises the window. Min/Max size constraints are disabled. +func (w *WebviewWindow) Maximise() Window { + if w.impl == nil || w.isDestroyed() { + w.options.StartState = WindowStateMaximised + return w + } + if !w.IsMaximised() { + w.DisableSizeConstraints() + InvokeSync(w.impl.maximise) + } + return w +} + +// UnMinimise un-minimises the window. Min/Max size constraints are re-enabled. +func (w *WebviewWindow) UnMinimise() { + if w.impl == nil || w.isDestroyed() { + return + } + if w.IsMinimised() { + InvokeSync(w.impl.unminimise) + } +} + +// UnMaximise un-maximises the window. Min/Max size constraints are re-enabled. +func (w *WebviewWindow) UnMaximise() { + if w.IsMaximised() { + w.EnableSizeConstraints() + InvokeSync(w.impl.unmaximise) + } +} + +// UnFullscreen un-fullscreens the window. Min/Max size constraints are re-enabled. +func (w *WebviewWindow) UnFullscreen() { + if w.IsFullscreen() { + w.EnableSizeConstraints() + InvokeSync(w.impl.unfullscreen) + } +} + +// Restore restores the window to its previous state if it was previously minimised, maximised or fullscreen. +func (w *WebviewWindow) Restore() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + if w.IsMinimised() { + w.UnMinimise() + } else if w.IsFullscreen() { + w.UnFullscreen() + } else if w.IsMaximised() { + w.UnMaximise() + } + }) +} + +func (w *WebviewWindow) DisableSizeConstraints() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + if w.options.MinWidth > 0 && w.options.MinHeight > 0 { + w.impl.setMinSize(0, 0) + } + if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 { + w.impl.setMaxSize(0, 0) + } + }) +} + +func (w *WebviewWindow) EnableSizeConstraints() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + if w.options.MinWidth > 0 && w.options.MinHeight > 0 { + w.SetMinSize(w.options.MinWidth, w.options.MinHeight) + } + if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 { + w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight) + } + }) +} + +// GetScreen returns the screen that the window is on +func (w *WebviewWindow) GetScreen() (*Screen, error) { + if w.impl == nil || w.isDestroyed() { + return nil, nil + } + return InvokeSyncWithResultAndError(w.impl.getScreen) +} + +// SetFrameless removes the window frame and title bar +func (w *WebviewWindow) SetFrameless(frameless bool) Window { + w.options.Frameless = frameless + if w.impl != nil { + InvokeSync(func() { + w.impl.setFrameless(frameless) + }) + } + return w +} + +func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) { + msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON()) + w.ExecJS(msg) +} + +func (w *WebviewWindow) dispatchWindowEvent(id uint) { + // TODO: Make this more efficient by keeping a list of which events have been registered + // and only dispatching those. + jsEvent := &CustomEvent{ + Name: events.JSEvent(id), + } + w.DispatchWailsEvent(jsEvent) +} + +func (w *WebviewWindow) Info(message string, args ...any) { + var messageArgs []interface{} + messageArgs = append(messageArgs, args...) + messageArgs = append(messageArgs, "sender", w.Name()) + globalApplication.info(message, messageArgs...) +} + +func (w *WebviewWindow) Error(message string, args ...any) { + args = append([]any{w.Name()}, args...) + globalApplication.error("in window '%s': "+message, args...) +} + +func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string) { + thisEvent := NewWindowEvent() + ctx := newWindowEventContext() + ctx.setDroppedFiles(filenames) + thisEvent.ctx = ctx + for _, listener := range w.eventListeners[uint(events.Common.WindowFilesDropped)] { + listener.callback(thisEvent) + } +} + +func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) { + // try application level context menu + menu, ok := globalApplication.ContextMenu.Get(data.Id) + if !ok { + w.Error("no context menu found for id: %s", data.Id) + return + } + menu.setContextData(data) + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.openContextMenu(menu.Menu, data) + }) +} + +// NativeWindowHandle returns the platform native window handle for the window. +func (w *WebviewWindow) NativeWindowHandle() (uintptr, error) { + if w.impl == nil || w.isDestroyed() { + return 0, errors.New("native handle unavailable as window is not running") + } + return w.impl.nativeWindowHandle(), nil +} + +func (w *WebviewWindow) Focus() { + InvokeSync(w.impl.focus) +} + +func (w *WebviewWindow) emit(eventType events.WindowEventType) { + windowEvents <- &windowEvent{ + WindowID: w.id, + EventID: uint(eventType), + } +} + +func (w *WebviewWindow) startDrag() error { + if w.impl == nil || w.isDestroyed() { + return nil + } + return InvokeSyncWithError(w.impl.startDrag) +} + +func (w *WebviewWindow) Print() error { + if w.impl == nil || w.isDestroyed() { + return nil + } + return InvokeSyncWithError(w.impl.print) +} + +func (w *WebviewWindow) SetEnabled(enabled bool) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.setEnabled(enabled) + }) +} + +func (w *WebviewWindow) processKeyBinding(acceleratorString string) bool { + // Check menu bindings + if w.menuBindings != nil { + w.menuBindingsLock.RLock() + defer w.menuBindingsLock.RUnlock() + if menuItem := w.menuBindings[acceleratorString]; menuItem != nil { + menuItem.handleClick() + return true + } + } + + // Check key bindings + if w.keyBindings != nil { + w.keyBindingsLock.RLock() + defer w.keyBindingsLock.RUnlock() + if callback := w.keyBindings[acceleratorString]; callback != nil { + // Execute callback + go func() { + defer handlePanic() + callback(w) + }() + return true + } + } + + return globalApplication.KeyBinding.Process(acceleratorString, w) +} + +func (w *WebviewWindow) HandleKeyEvent(acceleratorString string) { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(func() { + w.impl.handleKeyEvent(acceleratorString) + }) +} + +func (w *WebviewWindow) isDestroyed() bool { + w.destroyedLock.RLock() + defer w.destroyedLock.RUnlock() + return w.destroyed +} + +func (w *WebviewWindow) removeMenuBinding(a *accelerator) { + w.menuBindingsLock.Lock() + defer w.menuBindingsLock.Unlock() + w.menuBindings[a.String()] = nil +} + +func (w *WebviewWindow) addMenuBinding(a *accelerator, menuItem *MenuItem) { + w.menuBindingsLock.Lock() + defer w.menuBindingsLock.Unlock() + w.menuBindings[a.String()] = menuItem +} + +func (w *WebviewWindow) IsIgnoreMouseEvents() bool { + if w.impl == nil || w.isDestroyed() { + return false + } + return InvokeSyncWithResult(w.impl.isIgnoreMouseEvents) +} + +func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window { + w.options.IgnoreMouseEvents = ignore + if w.impl == nil || w.isDestroyed() { + return w + } + InvokeSync(func() { + w.impl.setIgnoreMouseEvents(ignore) + }) + return w +} + +func (w *WebviewWindow) cut() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.cut() +} + +func (w *WebviewWindow) copy() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.copy() +} + +func (w *WebviewWindow) paste() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.paste() +} + +func (w *WebviewWindow) selectAll() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.selectAll() +} + +func (w *WebviewWindow) undo() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.undo() +} + +func (w *WebviewWindow) delete() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.delete() +} + +func (w *WebviewWindow) redo() { + if w.impl == nil || w.isDestroyed() { + return + } + w.impl.redo() +} + +// ShowMenuBar shows the menu bar for the window. +func (w *WebviewWindow) ShowMenuBar() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.showMenuBar) +} + +// HideMenuBar hides the menu bar for the window. +func (w *WebviewWindow) HideMenuBar() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.hideMenuBar) +} + +// ToggleMenuBar toggles the menu bar for the window. +func (w *WebviewWindow) ToggleMenuBar() { + if w.impl == nil || w.isDestroyed() { + return + } + InvokeSync(w.impl.toggleMenuBar) +} diff --git a/v3/pkg/application/webview_window_close_darwin.go b/v3/pkg/application/webview_window_close_darwin.go new file mode 100644 index 000000000..d09833e93 --- /dev/null +++ b/v3/pkg/application/webview_window_close_darwin.go @@ -0,0 +1,26 @@ +//go:build darwin + +package application + +/* +#include +*/ +import "C" +import "sync/atomic" + +//export windowShouldUnconditionallyClose +func windowShouldUnconditionallyClose(windowId C.uint) C.bool { + window, _ := globalApplication.Window.GetByID(uint(windowId)) + if window == nil { + globalApplication.debug("windowShouldUnconditionallyClose: window not found", "windowId", windowId) + return C.bool(false) + } + webviewWindow, ok := window.(*WebviewWindow) + if !ok { + globalApplication.debug("windowShouldUnconditionallyClose: window is not WebviewWindow", "windowId", windowId) + return C.bool(false) + } + unconditionallyClose := atomic.LoadUint32(&webviewWindow.unconditionallyClose) != 0 + globalApplication.debug("windowShouldUnconditionallyClose check", "windowId", windowId, "unconditionallyClose", unconditionallyClose) + return C.bool(unconditionallyClose) +} diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go new file mode 100644 index 000000000..eb3f83769 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.go @@ -0,0 +1,1440 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "application_darwin.h" +#include "webview_window_darwin.h" +#include +#include "Cocoa/Cocoa.h" +#import +#import +#import "webview_window_darwin_drag.h" + +struct WebviewPreferences { + bool *TabFocusesLinks; + bool *TextInteractionEnabled; + bool *FullscreenEnabled; +}; + +extern void registerListener(unsigned int event); + +// Create a new Window +void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { + NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + if (frameless) { + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + } + WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + + // Allow fullscreen. Needed for frameless windows + window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary; + + // Create delegate + WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; + [delegate autorelease]; + + // Set delegate + [window setDelegate:delegate]; + delegate.windowId = id; + + // Add NSView to window + NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [view autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + if( frameless ) { + [view setWantsLayer:YES]; + view.layer.cornerRadius = 8.0; + } + [window setContentView:view]; + + // Embed wkwebview in window + NSRect frame = NSMakeRect(0, 0, width, height); + WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; + [config autorelease]; + + // Set preferences + if (preferences.TabFocusesLinks != NULL) { + config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; + } + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 + if (@available(macOS 11.3, *)) { + if (preferences.TextInteractionEnabled != NULL) { + config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; + } + } +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 + if (@available(macOS 12.3, *)) { + if (preferences.FullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; + } + } +#endif + + config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if (@available(macOS 10.15, *)) { + config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; + } +#endif + + // Setup user content controller + WKUserContentController* userContentController = [WKUserContentController new]; + [userContentController autorelease]; + + [userContentController addScriptMessageHandler:delegate name:@"external"]; + config.userContentController = userContentController; + + WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; + [webView autorelease]; + + [view addSubview:webView]; + + // support webview events + [webView setNavigationDelegate:delegate]; + + // Ensure webview resizes with the window + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + if( enableDragAndDrop ) { + WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [dragView autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [view addSubview:dragView]; + dragView.windowId = id; + } + + window.webView = webView; + return window; +} + + +void printWindowStyle(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSWindowStyleMask styleMask = [nsWindow styleMask]; + // Get delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + + printf("Window %d style mask: ", windowDelegate.windowId); + + if (styleMask & NSWindowStyleMaskTitled) + { + printf("NSWindowStyleMaskTitled "); + } + + if (styleMask & NSWindowStyleMaskClosable) + { + printf("NSWindowStyleMaskClosable "); + } + + if (styleMask & NSWindowStyleMaskMiniaturizable) + { + printf("NSWindowStyleMaskMiniaturizable "); + } + + if (styleMask & NSWindowStyleMaskResizable) + { + printf("NSWindowStyleMaskResizable "); + } + + if (styleMask & NSWindowStyleMaskFullSizeContentView) + { + printf("NSWindowStyleMaskFullSizeContentView "); + } + + if (styleMask & NSWindowStyleMaskNonactivatingPanel) + { + printf("NSWindowStyleMaskNonactivatingPanel "); + } + + if (styleMask & NSWindowStyleMaskFullScreen) + { + printf("NSWindowStyleMaskFullScreen "); + } + + if (styleMask & NSWindowStyleMaskBorderless) + { + printf("MSWindowStyleMaskBorderless "); + } + + printf("\n"); +} + + +// setInvisibleTitleBarHeight sets the invisible title bar height +void setInvisibleTitleBarHeight(void* window, unsigned int height) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // Get delegate + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate]; + // Set height + delegate.invisibleTitleBarHeight = height; +} + +// Make NSWindow transparent +void windowSetTransparent(void* nsWindow) { + [(WebviewWindow*)nsWindow setOpaque:NO]; + [(WebviewWindow*)nsWindow setBackgroundColor:[NSColor clearColor]]; +} + +void windowSetInvisibleTitleBar(void* nsWindow, unsigned int height) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + delegate.invisibleTitleBarHeight = height; +} + + +// Set the title of the NSWindow +void windowSetTitle(void* nsWindow, char* title) { + NSString* nsTitle = [NSString stringWithUTF8String:title]; + [(WebviewWindow*)nsWindow setTitle:nsTitle]; + free(title); +} + +// Set the size of the NSWindow +void windowSetSize(void* nsWindow, int width, int height) { + // Set window size on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size; + [window setContentSize:contentSize]; + [window setFrame:NSMakeRect(window.frame.origin.x, window.frame.origin.y, width, height) display:YES animate:YES]; +} + +// Set NSWindow always on top +void windowSetAlwaysOnTop(void* nsWindow, bool alwaysOnTop) { + // Set window always on top on main thread + [(WebviewWindow*)nsWindow setLevel:alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel]; +} + +void setNormalWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSNormalWindowLevel]; } +void setFloatingWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSFloatingWindowLevel];} +void setPopUpMenuWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSPopUpMenuWindowLevel]; } +void setMainMenuWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSMainMenuWindowLevel]; } +void setStatusWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSStatusWindowLevel]; } +void setModalPanelWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSModalPanelWindowLevel]; } +void setScreenSaverWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSScreenSaverWindowLevel]; } +void setTornOffMenuWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSTornOffMenuWindowLevel]; } + +// Load URL in NSWindow +void navigationLoadURL(void* nsWindow, char* url) { + // Load URL on main thread + NSURL* nsURL = [NSURL URLWithString:[NSString stringWithUTF8String:url]]; + NSURLRequest* request = [NSURLRequest requestWithURL:nsURL]; + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView loadRequest:request]; + free(url); +} + +// Set NSWindow resizable +void windowSetResizable(void* nsWindow, bool resizable) { + // Set window resizable on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + if (resizable) { + NSWindowStyleMask styleMask = [window styleMask] | NSWindowStyleMaskResizable; + [window setStyleMask:styleMask]; + } else { + NSWindowStyleMask styleMask = [window styleMask] & ~NSWindowStyleMaskResizable; + [window setStyleMask:styleMask]; + } +} + +// Set NSWindow min size +void windowSetMinSize(void* nsWindow, int width, int height) { + // Set window min size on main thread + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size; + [window setContentMinSize:contentSize]; + NSSize size = { width, height }; + [window setMinSize:size]; +} + +// Set NSWindow max size +void windowSetMaxSize(void* nsWindow, int width, int height) { + // Set window max size on main thread + NSSize size = { FLT_MAX, FLT_MAX }; + size.width = width > 0 ? width : FLT_MAX; + size.height = height > 0 ? height : FLT_MAX; + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, size.width, size.height)].size; + [window setContentMaxSize:contentSize]; + [window setMaxSize:size]; +} + +// windowZoomReset +void windowZoomReset(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView setMagnification:1.0]; +} + +// windowZoomSet +void windowZoomSet(void* nsWindow, double zoom) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Reset zoom + [window.webView setMagnification:zoom]; +} + +// windowZoomGet +float windowZoomGet(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Get zoom + return [window.webView magnification]; +} + +// windowZoomIn +void windowZoomIn(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Zoom in + [window.webView setMagnification:window.webView.magnification + 0.05]; +} + +// windowZoomOut +void windowZoomOut(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Zoom out + if( window.webView.magnification > 1.05 ) { + [window.webView setMagnification:window.webView.magnification - 0.05]; + } else { + [window.webView setMagnification:1.0]; + } +} + +// set the window position relative to the screen +void windowSetRelativePosition(void* nsWindow, int x, int y) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSScreen* screen = [window screen]; + if( screen == NULL ) { + screen = [NSScreen mainScreen]; + } + NSRect windowFrame = [window frame]; + NSRect screenFrame = [screen frame]; + windowFrame.origin.x = screenFrame.origin.x + (float)x; + windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y; + + [window setFrame:windowFrame display:TRUE animate:FALSE]; +} + +// Execute JS in NSWindow +void windowExecJS(void* nsWindow, const char* js) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil]; + free((void*)js); +} + +// Make NSWindow backdrop translucent +void windowSetTranslucent(void* nsWindow) { + // Get window + WebviewWindow* window = (WebviewWindow*)nsWindow; + + id contentView = [window contentView]; + NSVisualEffectView *effectView = [NSVisualEffectView alloc]; + NSRect bounds = [contentView bounds]; + [effectView initWithFrame:bounds]; + [effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow]; + [effectView setState:NSVisualEffectStateActive]; + [contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil]; +} + +// Make webview background transparent +void webviewSetTransparent(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Set webview background transparent + [window.webView setValue:@NO forKey:@"drawsBackground"]; +} + +// Set webview background colour +void webviewSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Set webview background color + [window.webView setValue:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0] forKey:@"backgroundColor"]; +} + +// Set the window background colour +void windowSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) { + [(WebviewWindow*)nsWindow setBackgroundColor:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0]]; +} + +bool windowIsMaximised(void* nsWindow) { + return [(WebviewWindow*)nsWindow isZoomed]; +} + +bool windowIsFullscreen(void* nsWindow) { + return [(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen; +} + +bool windowIsMinimised(void* nsWindow) { + return [(WebviewWindow*)nsWindow isMiniaturized]; +} + +bool windowIsFocused(void* nsWindow) { + return [(WebviewWindow*)nsWindow isKeyWindow]; +} + +// Set Window fullscreen +void windowFullscreen(void* nsWindow) { + if( windowIsFullscreen(nsWindow) ) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + });} + +void windowUnFullscreen(void* nsWindow) { + if( !windowIsFullscreen(nsWindow) ) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + }); +} + +// restore window to normal size +void windowRestore(void* nsWindow) { + // If window is fullscreen + if([(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen) { + [(WebviewWindow*)nsWindow toggleFullScreen:nil]; + } + // If window is maximised + if([(WebviewWindow*)nsWindow isZoomed]) { + [(WebviewWindow*)nsWindow zoom:nil]; + } + // If window in minimised + if([(WebviewWindow*)nsWindow isMiniaturized]) { + [(WebviewWindow*)nsWindow deminiaturize:nil]; + } +} + +// disable window fullscreen button +void setFullscreenButtonEnabled(void* nsWindow, bool enabled) { + NSButton *fullscreenButton = [(WebviewWindow*)nsWindow standardWindowButton:NSWindowZoomButton]; + fullscreenButton.enabled = enabled; +} + +// Set the titlebar style +void windowSetTitleBarAppearsTransparent(void* nsWindow, bool transparent) { + if( transparent ) { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:true]; + } else { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:false]; + } +} + +// Set window fullsize content view +void windowSetFullSizeContent(void* nsWindow, bool fullSize) { + if( fullSize ) { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView]; + } else { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView]; + } +} + +// Set Hide Titlebar +void windowSetHideTitleBar(void* nsWindow, bool hideTitlebar) { + if( hideTitlebar ) { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskTitled]; + } else { + [(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskTitled]; + } +} + +// Set Hide Title in Titlebar +void windowSetHideTitle(void* nsWindow, bool hideTitle) { + if( hideTitle ) { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleHidden]; + } else { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleVisible]; + } +} + +// Set Window use toolbar +void windowSetUseToolbar(void* nsWindow, bool useToolbar) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + if( useToolbar ) { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"]; + [toolbar autorelease]; + [window setToolbar:toolbar]; + } else { + [window setToolbar:nil]; + } +} + +// Set window toolbar style +void windowSetToolbarStyle(void* nsWindow, int style) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 + if (@available(macOS 11.0, *)) { + NSToolbar* toolbar = [window toolbar]; + if ( toolbar == nil ) { + return; + } + [window setToolbarStyle:style]; + } +#endif + +} +// Set Hide Toolbar Separator +void windowSetHideToolbarSeparator(void* nsWindow, bool hideSeparator) { + NSToolbar* toolbar = [(WebviewWindow*)nsWindow toolbar]; + if( toolbar == nil ) { + return; + } + [toolbar setShowsBaselineSeparator:!hideSeparator]; +} + +// Configure the toolbar auto-hide feature +void windowSetShowToolbarWhenFullscreen(void* window, bool setting) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // Get delegate + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate]; + // Set height + delegate.showToolbarWhenFullscreen = setting; +} + +// Set Window appearance type +void windowSetAppearanceTypeByName(void* nsWindow, const char *appearanceName) { + // set window appearance type by name + // Convert appearance name to NSString + NSString* appearanceNameString = [NSString stringWithUTF8String:appearanceName]; + // Set appearance + [(WebviewWindow*)nsWindow setAppearance:[NSAppearance appearanceNamed:appearanceNameString]]; + + free((void*)appearanceName); +} + +// Center window on current monitor +void windowCenter(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSScreen* screen = [window screen]; + if (screen == NULL) { + screen = [NSScreen mainScreen]; + } + + NSRect screenFrame = [screen frame]; + NSRect windowFrame = [window frame]; + + CGFloat x = screenFrame.origin.x + (screenFrame.size.width - windowFrame.size.width) / 2; + CGFloat y = screenFrame.origin.y + (screenFrame.size.height - windowFrame.size.height) / 2; + + [window setFrame:NSMakeRect(x, y, windowFrame.size.width, windowFrame.size.height) display:YES]; +} + + +// Get the current size of the window +void windowGetSize(void* nsWindow, int* width, int* height) { + NSRect frame = [(WebviewWindow*)nsWindow frame]; + *width = frame.size.width; + *height = frame.size.height; +} + +// Get window width +int windowGetWidth(void* nsWindow) { + return [(WebviewWindow*)nsWindow frame].size.width; +} + +// Get window height +int windowGetHeight(void* nsWindow) { + return [(WebviewWindow*)nsWindow frame].size.height; +} + +// Get window position +void windowGetRelativePosition(void* nsWindow, int* x, int* y) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSRect frame = [window frame]; + *x = frame.origin.x; + + // Translate to screen coordinates so Y=0 is the top of the screen + NSScreen* screen = [window screen]; + if( screen == NULL ) { + screen = [NSScreen mainScreen]; + } + NSRect screenFrame = [screen frame]; + *y = screenFrame.size.height - frame.origin.y - frame.size.height; +} + +// Get absolute window position +void windowGetPosition(void* nsWindow, int* x, int* y) { + NSRect frame = [(WebviewWindow*)nsWindow frame]; + *x = frame.origin.x; + *y = frame.origin.y; +} + +void windowSetPosition(void* nsWindow, int x, int y) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSScreen* screen = [window screen]; + if (screen == NULL) { + screen = [NSScreen mainScreen]; + } + // Get the scale of the screen + CGFloat scale = [screen backingScaleFactor]; + NSRect frame = [window frame]; + // Scale the position + frame.origin.x = x / scale; + frame.origin.y = (screen.frame.size.height - frame.size.height) - (y / scale); + // Set the frame + [window setFrame:frame display:YES]; +} + + +// Destroy window +void windowDestroy(void* nsWindow) { + [(WebviewWindow*)nsWindow close]; +} + +// Remove drop shadow from window +void windowSetShadow(void* nsWindow, bool hasShadow) { + [(WebviewWindow*)nsWindow setHasShadow:hasShadow]; +} + + + +// windowClose closes the current window +static void windowClose(void *window) { + [(WebviewWindow*)window close]; +} + +// windowZoom +static void windowZoom(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +// webviewRenderHTML renders the given HTML +static void windowRenderHTML(void *window, const char *html) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // get window delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + // render html + [nsWindow.webView loadHTMLString:[NSString stringWithUTF8String:html] baseURL:nil]; +} + +static void windowInjectCSS(void *window, const char *css) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // inject css + [nsWindow.webView evaluateJavaScript:[NSString stringWithFormat:@"(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%@')); document.head.appendChild(style); })();", [NSString stringWithUTF8String:css]] completionHandler:nil]; + free((void*)css); +} + +static void windowMinimise(void *window) { + [(WebviewWindow*)window miniaturize:nil]; +} + +// zoom maximizes the window to the screen dimensions +static void windowMaximise(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +static bool isFullScreen(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + long mask = [nsWindow styleMask]; + return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen; +} + +static bool isVisible(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + return (nsWindow.occlusionState & NSWindowOcclusionStateVisible) == NSWindowOcclusionStateVisible; +} + +// windowSetFullScreen +static void windowSetFullScreen(void *window, bool fullscreen) { + if (isFullScreen(window)) { + return; + } + WebviewWindow* nsWindow = (WebviewWindow*)window; + windowSetMaxSize(nsWindow, 0, 0); + windowSetMinSize(nsWindow, 0, 0); + [nsWindow toggleFullScreen:nil]; +} + +// windowUnminimise +static void windowUnminimise(void *window) { + [(WebviewWindow*)window deminiaturize:nil]; +} + +// windowUnmaximise +static void windowUnmaximise(void *window) { + [(WebviewWindow*)window zoom:nil]; +} + +static void windowDisableSizeConstraints(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // disable size constraints + [nsWindow setContentMinSize:CGSizeZero]; + [nsWindow setContentMaxSize:CGSizeZero]; +} + +static void windowShow(void *window) { + [(WebviewWindow*)window makeKeyAndOrderFront:nil]; +} + +static void windowHide(void *window) { + [(WebviewWindow*)window orderOut:nil]; +} + +// setButtonState sets the state of the given button +// 0 = enabled +// 1 = disabled +// 2 = hidden +static void setButtonState(void *button, int state) { + if (button == nil) { + return; + } + NSButton *nsbutton = (NSButton*)button; + nsbutton.hidden = state == 2; + nsbutton.enabled = state != 1; +} + +// setMinimiseButtonState sets the minimise button state +static void setMinimiseButtonState(void *window, int state) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSButton *minimiseButton = [nsWindow standardWindowButton:NSWindowMiniaturizeButton]; + setButtonState(minimiseButton, state); +} + +// setMaximiseButtonState sets the maximise button state +static void setMaximiseButtonState(void *window, int state) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSButton *maximiseButton = [nsWindow standardWindowButton:NSWindowZoomButton]; + setButtonState(maximiseButton, state); +} + +// setCloseButtonState sets the close button state +static void setCloseButtonState(void *window, int state) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSButton *closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; + setButtonState(closeButton, state); +} + +// windowShowMenu opens an NSMenu at the given coordinates +static void windowShowMenu(void *window, void *menu, int x, int y) { + NSMenu* nsMenu = (NSMenu*)menu; + WKWebView* webView = ((WebviewWindow*)window).webView; + NSPoint point = NSMakePoint(x, y); + [nsMenu popUpMenuPositioningItem:nil atLocation:point inView:webView]; +} + +// Make the given window frameless +static void windowSetFrameless(void *window, bool frameless) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // set the window style to be frameless + if (frameless) { + [nsWindow setStyleMask:([nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView)]; + } else { + [nsWindow setStyleMask:([nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView)]; + } +} + +static void startDrag(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + + // Get delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + + // start drag + [windowDelegate startDrag:nsWindow]; +} + +// Credit: https://stackoverflow.com/q/33319295 +static void windowPrint(void *window) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 + // Check if macOS 11.0 or newer + if (@available(macOS 11.0, *)) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + WKWebView* webView = nsWindow.webView; + + // TODO: Think about whether to expose this as config + NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo]; + pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic; + pInfo.verticalPagination = NSPrintingPaginationModeAutomatic; + pInfo.verticallyCentered = YES; + pInfo.horizontallyCentered = YES; + pInfo.orientation = NSPaperOrientationLandscape; + pInfo.leftMargin = 30; + pInfo.rightMargin = 30; + pInfo.topMargin = 30; + pInfo.bottomMargin = 30; + + NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo]; + po.showsPrintPanel = YES; + po.showsProgressPanel = YES; + + // Without the next line you get an exception. Also it seems to + // completely ignore the values in the rect. I tried changing them + // in both x and y direction to include content scrolled off screen. + // It had no effect whatsoever in either direction. + po.view.frame = webView.bounds; + + // [printOperation runOperation] DOES NOT WORK WITH WKWEBVIEW, use + [po runOperationModalForWindow:window delegate:windowDelegate didRunSelector:nil contextInfo:nil]; + } +#endif +} + +void setWindowEnabled(void *window, bool enabled) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + [nsWindow setIgnoresMouseEvents:!enabled]; +} + +void windowSetEnabled(void *window, bool enabled) { + // TODO: Implement +} + +void windowFocus(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + // If the current application is not active, activate it + if (![[NSApplication sharedApplication] isActive]) { + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + } + [nsWindow makeKeyAndOrderFront:nil]; + [nsWindow makeKeyWindow]; +} + +static bool isIgnoreMouseEvents(void *nsWindow) { + NSWindow *window = (__bridge NSWindow *)nsWindow; + return [window ignoresMouseEvents]; +} + +static void setIgnoreMouseEvents(void *nsWindow, bool ignore) { + NSWindow *window = (__bridge NSWindow *)nsWindow; + [window setIgnoresMouseEvents:ignore]; +} + +*/ +import "C" +import ( + "sync" + "sync/atomic" + "unsafe" + + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/runtime" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +type macosWebviewWindow struct { + nsWindow unsafe.Pointer + parent *WebviewWindow +} + +func (w *macosWebviewWindow) handleKeyEvent(acceleratorString string) { + // Parse acceleratorString + accelerator, err := parseAccelerator(acceleratorString) + if err != nil { + globalApplication.error("unable to parse accelerator: %w", err) + return + } + w.parent.processKeyBinding(accelerator.String()) +} + +func (w *macosWebviewWindow) getBorderSizes() *LRTB { + return &LRTB{} +} + +func (w *macosWebviewWindow) isFocused() bool { + return bool(C.windowIsFocused(w.nsWindow)) +} + +func (w *macosWebviewWindow) setPosition(x int, y int) { + C.windowSetPosition(w.nsWindow, C.int(x), C.int(y)) +} + +func (w *macosWebviewWindow) print() error { + C.windowPrint(w.nsWindow) + return nil +} + +func (w *macosWebviewWindow) startResize(_ string) error { + // Never called. Handled natively by the OS. + return nil +} + +func (w *macosWebviewWindow) focus() { + // Make the window key and main + C.windowFocus(w.nsWindow) +} + +func (w *macosWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { + // Create the menu + thisMenu := newMenuImpl(menu) + thisMenu.update() + C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y)) +} + +func (w *macosWebviewWindow) getZoom() float64 { + return float64(C.windowZoomGet(w.nsWindow)) +} + +func (w *macosWebviewWindow) setZoom(zoom float64) { + C.windowZoomSet(w.nsWindow, C.double(zoom)) +} + +func (w *macosWebviewWindow) setFrameless(frameless bool) { + C.windowSetFrameless(w.nsWindow, C.bool(frameless)) + if frameless { + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(true)) + C.windowSetHideTitle(w.nsWindow, C.bool(true)) + } else { + macOptions := w.parent.options.Mac + appearsTransparent := macOptions.TitleBar.AppearsTransparent + hideTitle := macOptions.TitleBar.HideTitle + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(appearsTransparent)) + C.windowSetHideTitle(w.nsWindow, C.bool(hideTitle)) + } +} + +func (w *macosWebviewWindow) setHasShadow(hasShadow bool) { + C.windowSetShadow(w.nsWindow, C.bool(hasShadow)) +} + +func (w *macosWebviewWindow) getScreen() (*Screen, error) { + return getScreenForWindow(w) +} + +func (w *macosWebviewWindow) show() { + C.windowShow(w.nsWindow) +} + +func (w *macosWebviewWindow) hide() { + globalApplication.debug("Window hiding", "windowId", w.parent.id, "title", w.parent.options.Title) + C.windowHide(w.nsWindow) +} + +func (w *macosWebviewWindow) setFullscreenButtonEnabled(enabled bool) { + C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled)) +} + +func (w *macosWebviewWindow) disableSizeConstraints() { + C.windowDisableSizeConstraints(w.nsWindow) +} + +func (w *macosWebviewWindow) unfullscreen() { + C.windowUnFullscreen(w.nsWindow) +} + +func (w *macosWebviewWindow) fullscreen() { + C.windowFullscreen(w.nsWindow) +} + +func (w *macosWebviewWindow) unminimise() { + C.windowUnminimise(w.nsWindow) +} + +func (w *macosWebviewWindow) unmaximise() { + C.windowUnmaximise(w.nsWindow) +} + +func (w *macosWebviewWindow) maximise() { + C.windowMaximise(w.nsWindow) +} + +func (w *macosWebviewWindow) minimise() { + C.windowMinimise(w.nsWindow) +} + +func (w *macosWebviewWindow) on(eventID uint) { + //C.registerListener(C.uint(eventID)) +} + +func (w *macosWebviewWindow) zoom() { + C.windowZoom(w.nsWindow) +} + +func (w *macosWebviewWindow) windowZoom() { + C.windowZoom(w.nsWindow) +} + +func (w *macosWebviewWindow) close() { + globalApplication.debug("Window close() called - setting unconditionallyClose flag", "windowId", w.parent.id, "title", w.parent.options.Title) + // Set the unconditionallyClose flag to allow the window to close + atomic.StoreUint32(&w.parent.unconditionallyClose, 1) + C.windowClose(w.nsWindow) + globalApplication.debug("Window close() completed", "windowId", w.parent.id, "title", w.parent.options.Title) + // TODO: Check if we need to unregister the window here or not +} + +func (w *macosWebviewWindow) zoomIn() { + C.windowZoomIn(w.nsWindow) +} + +func (w *macosWebviewWindow) zoomOut() { + C.windowZoomOut(w.nsWindow) +} + +func (w *macosWebviewWindow) zoomReset() { + C.windowZoomReset(w.nsWindow) +} + +func (w *macosWebviewWindow) reload() { + //TODO: Implement + globalApplication.debug("reload called on WebviewWindow", "parentID", w.parent.id) +} + +func (w *macosWebviewWindow) forceReload() { + //TODO: Implement + globalApplication.debug("force reload called on WebviewWindow", "parentID", w.parent.id) +} + +func (w *macosWebviewWindow) center() { + C.windowCenter(w.nsWindow) +} + +func (w *macosWebviewWindow) isMinimised() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsMinimised(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isMaximised() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsMaximised(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isFullscreen() bool { + return w.syncMainThreadReturningBool(func() bool { + return bool(C.windowIsFullscreen(w.nsWindow)) + }) +} + +func (w *macosWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *macosWebviewWindow) isVisible() bool { + return bool(C.isVisible(w.nsWindow)) +} + +func (w *macosWebviewWindow) syncMainThreadReturningBool(fn func() bool) bool { + var wg sync.WaitGroup + wg.Add(1) + var result bool + globalApplication.dispatchOnMainThread(func() { + result = fn() + wg.Done() + }) + wg.Wait() + return result +} + +func (w *macosWebviewWindow) restore() { + // restore window to normal size + C.windowRestore(w.nsWindow) +} + +func (w *macosWebviewWindow) restoreWindow() { + C.windowRestore(w.nsWindow) +} + +func (w *macosWebviewWindow) setEnabled(enabled bool) { + C.windowSetEnabled(w.nsWindow, C.bool(enabled)) +} + +func (w *macosWebviewWindow) execJS(js string) { + InvokeAsync(func() { + globalApplication.shutdownLock.Lock() + performingShutdown := globalApplication.performingShutdown + globalApplication.shutdownLock.Unlock() + + if performingShutdown { + return + } + if w.nsWindow == nil || w.parent.isDestroyed() { + return + } + C.windowExecJS(w.nsWindow, C.CString(js)) + }) +} + +func (w *macosWebviewWindow) setURL(uri string) { + C.navigationLoadURL(w.nsWindow, C.CString(uri)) +} + +func (w *macosWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + C.windowSetAlwaysOnTop(w.nsWindow, C.bool(alwaysOnTop)) +} + +func newWindowImpl(parent *WebviewWindow) *macosWebviewWindow { + result := &macosWebviewWindow{ + parent: parent, + } + result.parent.RegisterHook(events.Mac.WebViewDidFinishNavigation, func(event *WindowEvent) { + result.execJS(runtime.Core()) + }) + return result +} + +func (w *macosWebviewWindow) setTitle(title string) { + if !w.parent.options.Frameless { + cTitle := C.CString(title) + C.windowSetTitle(w.nsWindow, cTitle) + } +} + +func (w *macosWebviewWindow) flash(_ bool) { + // Not supported on macOS +} + +func (w *macosWebviewWindow) setSize(width, height int) { + C.windowSetSize(w.nsWindow, C.int(width), C.int(height)) +} + +func (w *macosWebviewWindow) setMinSize(width, height int) { + C.windowSetMinSize(w.nsWindow, C.int(width), C.int(height)) +} +func (w *macosWebviewWindow) setMaxSize(width, height int) { + C.windowSetMaxSize(w.nsWindow, C.int(width), C.int(height)) +} + +func (w *macosWebviewWindow) setResizable(resizable bool) { + C.windowSetResizable(w.nsWindow, C.bool(resizable)) +} + +func (w *macosWebviewWindow) size() (int, int) { + var width, height C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + C.windowGetSize(w.nsWindow, &width, &height) + wg.Done() + }) + wg.Wait() + return int(width), int(height) +} + +func (w *macosWebviewWindow) setRelativePosition(x, y int) { + C.windowSetRelativePosition(w.nsWindow, C.int(x), C.int(y)) +} + +func (w *macosWebviewWindow) setWindowLevel(level MacWindowLevel) { + switch level { + case MacWindowLevelNormal: + C.setNormalWindowLevel(w.nsWindow) + case MacWindowLevelFloating: + C.setFloatingWindowLevel(w.nsWindow) + case MacWindowLevelTornOffMenu: + C.setTornOffMenuWindowLevel(w.nsWindow) + case MacWindowLevelModalPanel: + C.setModalPanelWindowLevel(w.nsWindow) + case MacWindowLevelMainMenu: + C.setMainMenuWindowLevel(w.nsWindow) + case MacWindowLevelStatus: + C.setStatusWindowLevel(w.nsWindow) + case MacWindowLevelPopUpMenu: + C.setPopUpMenuWindowLevel(w.nsWindow) + case MacWindowLevelScreenSaver: + C.setScreenSaverWindowLevel(w.nsWindow) + } +} + +func (w *macosWebviewWindow) width() int { + var width C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + width = C.windowGetWidth(w.nsWindow) + wg.Done() + }) + wg.Wait() + return int(width) +} +func (w *macosWebviewWindow) height() int { + var height C.int + var wg sync.WaitGroup + wg.Add(1) + globalApplication.dispatchOnMainThread(func() { + height = C.windowGetHeight(w.nsWindow) + wg.Done() + }) + wg.Wait() + return int(height) +} + +func bool2CboolPtr(value bool) *C.bool { + v := C.bool(value) + return &v +} + +func (w *macosWebviewWindow) getWebviewPreferences() C.struct_WebviewPreferences { + wvprefs := w.parent.options.Mac.WebviewPreferences + + var result C.struct_WebviewPreferences + + if wvprefs.TextInteractionEnabled.IsSet() { + result.TextInteractionEnabled = bool2CboolPtr(wvprefs.TextInteractionEnabled.Get()) + } + if wvprefs.TabFocusesLinks.IsSet() { + result.TabFocusesLinks = bool2CboolPtr(wvprefs.TabFocusesLinks.Get()) + } + if wvprefs.FullscreenEnabled.IsSet() { + result.FullscreenEnabled = bool2CboolPtr(wvprefs.FullscreenEnabled.Get()) + } + + return result +} + +func (w *macosWebviewWindow) run() { + for eventId := range w.parent.eventListeners { + w.on(eventId) + } + globalApplication.dispatchOnMainThread(func() { + options := w.parent.options + macOptions := options.Mac + + w.nsWindow = C.windowNew(C.uint(w.parent.id), + C.int(options.Width), + C.int(options.Height), + C.bool(macOptions.EnableFraudulentWebsiteWarnings), + C.bool(options.Frameless), + C.bool(options.EnableDragAndDrop), + w.getWebviewPreferences(), + ) + w.setTitle(options.Title) + w.setResizable(!options.DisableResize) + if options.MinWidth != 0 || options.MinHeight != 0 { + w.setMinSize(options.MinWidth, options.MinHeight) + } + if options.MaxWidth != 0 || options.MaxHeight != 0 { + w.setMaxSize(options.MaxWidth, options.MaxHeight) + } + //w.setZoom(options.Zoom) + w.enableDevTools() + + w.setBackgroundColour(options.BackgroundColour) + + switch macOptions.Backdrop { + case MacBackdropTransparent: + C.windowSetTransparent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + case MacBackdropTranslucent: + C.windowSetTranslucent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + case MacBackdropNormal: + } + + if macOptions.WindowLevel == "" { + macOptions.WindowLevel = MacWindowLevelNormal + } + w.setWindowLevel(macOptions.WindowLevel) + + // Initialise the window buttons + w.setMinimiseButtonState(options.MinimiseButtonState) + w.setMaximiseButtonState(options.MaximiseButtonState) + w.setCloseButtonState(options.CloseButtonState) + + // Ignore mouse events if requested + w.setIgnoreMouseEvents(options.IgnoreMouseEvents) + + titleBarOptions := macOptions.TitleBar + if !w.parent.options.Frameless { + C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(titleBarOptions.AppearsTransparent)) + C.windowSetHideTitleBar(w.nsWindow, C.bool(titleBarOptions.Hide)) + C.windowSetHideTitle(w.nsWindow, C.bool(titleBarOptions.HideTitle)) + C.windowSetFullSizeContent(w.nsWindow, C.bool(titleBarOptions.FullSizeContent)) + C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar)) + C.windowSetToolbarStyle(w.nsWindow, C.int(titleBarOptions.ToolbarStyle)) + C.windowSetShowToolbarWhenFullscreen(w.nsWindow, C.bool(titleBarOptions.ShowToolbarWhenFullscreen)) + C.windowSetHideToolbarSeparator(w.nsWindow, C.bool(titleBarOptions.HideToolbarSeparator)) + } + + if macOptions.Appearance != "" { + C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance))) + } + + if macOptions.InvisibleTitleBarHeight != 0 { + C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight)) + } + + switch w.parent.options.StartState { + case WindowStateMaximised: + w.maximise() + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + case WindowStateNormal: + } + if w.parent.options.InitialPosition == WindowCentered { + C.windowCenter(w.nsWindow) + } else { + w.setPosition(options.X, options.Y) + } + + startURL, err := assetserver.GetStartURL(options.URL) + if err != nil { + globalApplication.handleFatalError(err) + } + + w.setURL(startURL) + + // We need to wait for the HTML to load before we can execute the javascript + w.parent.OnWindowEvent(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) { + InvokeAsync(func() { + if options.JS != "" { + w.execJS(options.JS) + } + if options.CSS != "" { + C.windowInjectCSS(w.nsWindow, C.CString(options.CSS)) + } + if !options.Hidden { + w.parent.Show() + w.setHasShadow(!options.Mac.DisableShadow) + w.setAlwaysOnTop(options.AlwaysOnTop) + } else { + // We have to wait until the window is shown before we can remove the shadow + var cancel func() + cancel = w.parent.OnWindowEvent(events.Mac.WindowDidBecomeKey, func(_ *WindowEvent) { + InvokeAsync(func() { + if !w.isVisible() { + w.parent.Show() + } + w.setHasShadow(!options.Mac.DisableShadow) + w.setAlwaysOnTop(options.AlwaysOnTop) + cancel() + }) + }) + } + }) + }) + + if options.HTML != "" { + w.setHTML(options.HTML) + } + + }) +} + +func (w *macosWebviewWindow) nativeWindowHandle() uintptr { + return uintptr(w.nsWindow) +} + +func (w *macosWebviewWindow) setBackgroundColour(colour RGBA) { + + C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha)) +} + +func (w *macosWebviewWindow) relativePosition() (int, int) { + var x, y C.int + InvokeSync(func() { + C.windowGetRelativePosition(w.nsWindow, &x, &y) + }) + + return int(x), int(y) +} + +func (w *macosWebviewWindow) position() (int, int) { + var x, y C.int + InvokeSync(func() { + C.windowGetPosition(w.nsWindow, &x, &y) + }) + + return int(x), int(y) +} + +func (w *macosWebviewWindow) bounds() Rect { + // DOTO: do it in a single step + proper DPI scaling + var x, y, width, height C.int + InvokeSync(func() { + C.windowGetPosition(w.nsWindow, &x, &y) + C.windowGetSize(w.nsWindow, &width, &height) + }) + + return Rect{ + X: int(x), + Y: int(y), + Width: int(width), + Height: int(height), + } +} + +func (w *macosWebviewWindow) setBounds(bounds Rect) { + // DOTO: do it in a single step + proper DPI scaling + C.windowSetPosition(w.nsWindow, C.int(bounds.X), C.int(bounds.Y)) + C.windowSetSize(w.nsWindow, C.int(bounds.Width), C.int(bounds.Height)) +} + +func (w *macosWebviewWindow) physicalBounds() Rect { + // TODO: proper DPI scaling + return w.bounds() +} + +func (w *macosWebviewWindow) setPhysicalBounds(physicalBounds Rect) { + // TODO: proper DPI scaling + w.setBounds(physicalBounds) +} + +func (w *macosWebviewWindow) destroy() { + w.parent.markAsDestroyed() + C.windowDestroy(w.nsWindow) +} + +func (w *macosWebviewWindow) setHTML(html string) { + // Convert HTML to C string + cHTML := C.CString(html) + // Render HTML + C.windowRenderHTML(w.nsWindow, cHTML) +} + +func (w *macosWebviewWindow) startDrag() error { + C.startDrag(w.nsWindow) + return nil +} + +func (w *macosWebviewWindow) setMinimiseButtonState(state ButtonState) { + C.setMinimiseButtonState(w.nsWindow, C.int(state)) +} + +func (w *macosWebviewWindow) setMaximiseButtonState(state ButtonState) { + C.setMaximiseButtonState(w.nsWindow, C.int(state)) +} + +func (w *macosWebviewWindow) setCloseButtonState(state ButtonState) { + C.setCloseButtonState(w.nsWindow, C.int(state)) +} + +func (w *macosWebviewWindow) isIgnoreMouseEvents() bool { + return bool(C.isIgnoreMouseEvents(w.nsWindow)) +} + +func (w *macosWebviewWindow) setIgnoreMouseEvents(ignore bool) { + C.setIgnoreMouseEvents(w.nsWindow, C.bool(ignore)) +} + +func (w *macosWebviewWindow) cut() { +} + +func (w *macosWebviewWindow) paste() { +} + +func (w *macosWebviewWindow) copy() { +} + +func (w *macosWebviewWindow) selectAll() { +} + +func (w *macosWebviewWindow) undo() { +} + +func (w *macosWebviewWindow) delete() { +} + +func (w *macosWebviewWindow) redo() { +} + +func (w *macosWebviewWindow) showMenuBar() {} +func (w *macosWebviewWindow) hideMenuBar() {} +func (w *macosWebviewWindow) toggleMenuBar() {} +func (w *macosWebviewWindow) setMenu(_ *Menu) {} diff --git a/v3/pkg/application/webview_window_darwin.h b/v3/pkg/application/webview_window_darwin.h new file mode 100644 index 000000000..f0f58d896 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.h @@ -0,0 +1,37 @@ +//go:build darwin + +#ifndef WebviewWindowDelegate_h +#define WebviewWindowDelegate_h + +#import +#import + +@interface WebviewWindow : NSWindow +- (BOOL) canBecomeKeyWindow; +- (BOOL) canBecomeMainWindow; +- (BOOL) acceptsFirstResponder; +- (BOOL) becomeFirstResponder; +- (BOOL) resignFirstResponder; +- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; + +@property (assign) WKWebView* webView; // We already retain WKWebView since it's part of the Window. + +@end + +@interface WebviewWindowDelegate : NSObject + +@property unsigned int windowId; +@property (retain) NSEvent* leftMouseEvent; +@property unsigned int invisibleTitleBarHeight; +@property BOOL showToolbarWhenFullscreen; +@property NSWindowStyleMask previousStyleMask; // Used to restore the window style mask when using frameless + +- (void)handleLeftMouseUp:(NSWindow *)window; +- (void)handleLeftMouseDown:(NSEvent*)event; +- (void)startDrag:(WebviewWindow*)window; + +@end + +void windowSetScreen(void* window, void* screen, int yOffset); + +#endif /* WebviewWindowDelegate_h */ diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m new file mode 100644 index 000000000..bdd5d8810 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.m @@ -0,0 +1,728 @@ +//go:build darwin +#import +#import +#import "webview_window_darwin.h" +#import "../events/events_darwin.h" +extern void processMessage(unsigned int, const char*); +extern void processURLRequest(unsigned int, void *); +extern void processWindowKeyDownEvent(unsigned int, const char*); +extern bool hasListeners(unsigned int); +extern bool windowShouldUnconditionallyClose(unsigned int); +@implementation WebviewWindow +- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +{ + self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation]; + [self setAlphaValue:1.0]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setOpaque:NO]; + [self setMovableByWindowBackground:YES]; + return self; +} +- (void)keyDown:(NSEvent *)event { + NSUInteger modifierFlags = event.modifierFlags; + // Create an array to hold the modifier strings + NSMutableArray *modifierStrings = [NSMutableArray array]; + // Check for modifier flags and add corresponding strings to the array + if (modifierFlags & NSEventModifierFlagShift) { + [modifierStrings addObject:@"shift"]; + } + if (modifierFlags & NSEventModifierFlagControl) { + [modifierStrings addObject:@"ctrl"]; + } + if (modifierFlags & NSEventModifierFlagOption) { + [modifierStrings addObject:@"option"]; + } + if (modifierFlags & NSEventModifierFlagCommand) { + [modifierStrings addObject:@"cmd"]; + } + NSString *keyString = [self keyStringFromEvent:event]; + if (keyString.length > 0) { + [modifierStrings addObject:keyString]; + } + // Combine the modifier strings with the key character + NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; + const char* utf8String = [keyEventString UTF8String]; + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; + processWindowKeyDownEvent(delegate.windowId, utf8String); +} +- (NSString *)keyStringFromEvent:(NSEvent *)event { + // Get the pressed key + // Check for special keys like escape and tab + NSString *characters = [event characters]; + if (characters.length == 0) { + return @""; + } + if ([characters isEqualToString:@"\r"]) { + return @"enter"; + } + if ([characters isEqualToString:@"\b"]) { + return @"backspace"; + } + if ([characters isEqualToString:@"\e"]) { + return @"escape"; + } + // page down + if ([characters isEqualToString:@"\x0B"]) { + return @"page down"; + } + // page up + if ([characters isEqualToString:@"\x0E"]) { + return @"page up"; + } + // home + if ([characters isEqualToString:@"\x01"]) { + return @"home"; + } + // end + if ([characters isEqualToString:@"\x04"]) { + return @"end"; + } + // clear + if ([characters isEqualToString:@"\x0C"]) { + return @"clear"; + } + switch ([event keyCode]) { + // Function keys + case 122: return @"f1"; + case 120: return @"f2"; + case 99: return @"f3"; + case 118: return @"f4"; + case 96: return @"f5"; + case 97: return @"f6"; + case 98: return @"f7"; + case 100: return @"f8"; + case 101: return @"f9"; + case 109: return @"f10"; + case 103: return @"f11"; + case 111: return @"f12"; + case 105: return @"f13"; + case 107: return @"f14"; + case 113: return @"f15"; + case 106: return @"f16"; + case 64: return @"f17"; + case 79: return @"f18"; + case 80: return @"f19"; + case 90: return @"f20"; + // Letter keys + case 0: return @"a"; + case 11: return @"b"; + case 8: return @"c"; + case 2: return @"d"; + case 14: return @"e"; + case 3: return @"f"; + case 5: return @"g"; + case 4: return @"h"; + case 34: return @"i"; + case 38: return @"j"; + case 40: return @"k"; + case 37: return @"l"; + case 46: return @"m"; + case 45: return @"n"; + case 31: return @"o"; + case 35: return @"p"; + case 12: return @"q"; + case 15: return @"r"; + case 1: return @"s"; + case 17: return @"t"; + case 32: return @"u"; + case 9: return @"v"; + case 13: return @"w"; + case 7: return @"x"; + case 16: return @"y"; + case 6: return @"z"; + // Number keys + case 29: return @"0"; + case 18: return @"1"; + case 19: return @"2"; + case 20: return @"3"; + case 21: return @"4"; + case 23: return @"5"; + case 22: return @"6"; + case 26: return @"7"; + case 28: return @"8"; + case 25: return @"9"; + // Other special keys + case 51: return @"delete"; + case 117: return @"forward delete"; + case 123: return @"left"; + case 124: return @"right"; + case 126: return @"up"; + case 125: return @"down"; + case 48: return @"tab"; + case 53: return @"escape"; + case 49: return @"space"; + // Punctuation and other keys (for a standard US layout) + case 33: return @"["; + case 30: return @"]"; + case 43: return @","; + case 27: return @"-"; + case 39: return @"'"; + case 44: return @"/"; + case 47: return @"."; + case 41: return @";"; + case 24: return @"="; + case 50: return @"`"; + case 42: return @"\\"; + default: return @""; + } +} +- (BOOL)canBecomeKeyWindow { + return YES; +} +- (BOOL) canBecomeMainWindow { + return YES; +} +- (BOOL) acceptsFirstResponder { + return YES; +} +- (BOOL) becomeFirstResponder { + return YES; +} +- (BOOL) resignFirstResponder { + return YES; +} +- (void) setDelegate:(id) delegate { + [delegate retain]; + [super setDelegate: delegate]; +} +- (void) dealloc { + // Remove the script handler, otherwise WebviewWindowDelegate won't get deallocated + // See: https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak + [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"external"]; + if (self.delegate) { + [self.delegate release]; + } + [super dealloc]; +} +- (void)windowDidZoom:(NSNotification *)notification { + NSWindow *window = notification.object; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + if ([window isZoomed]) { + if (hasListeners(EventWindowMaximise)) { + processWindowEvent(delegate.windowId, EventWindowMaximise); + } + } else { + if (hasListeners(EventWindowUnMaximise)) { + processWindowEvent(delegate.windowId, EventWindowUnMaximise); + } + } +} +- (void)performZoomIn:(id)sender { + [super zoom:sender]; + if (hasListeners(EventWindowZoomIn)) { + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate]; + processWindowEvent(delegate.windowId, EventWindowZoomIn); + } +} +- (void)performZoomOut:(id)sender { + [super zoom:sender]; + if (hasListeners(EventWindowZoomOut)) { + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate]; + processWindowEvent(delegate.windowId, EventWindowZoomOut); + } +} +- (void)performZoomReset:(id)sender { + [self setFrame:[self frameRectForContentRect:[[self screen] visibleFrame]] display:YES]; + if (hasListeners(EventWindowZoomReset)) { + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate]; + processWindowEvent(delegate.windowId, EventWindowZoomReset); + } +} +@end +@implementation WebviewWindowDelegate +- (BOOL)windowShouldClose:(NSWindow *)sender { + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate]; + NSLog(@"[DEBUG] windowShouldClose called for window %d", delegate.windowId); + // Check if this window should close unconditionally (called from Close() method) + if (windowShouldUnconditionallyClose(delegate.windowId)) { + NSLog(@"[DEBUG] Window %d closing unconditionally (Close() method called)", delegate.windowId); + return true; + } + // For user-initiated closes, emit WindowClosing event and let the application decide + NSLog(@"[DEBUG] Window %d close requested by user - emitting WindowClosing event", delegate.windowId); + processWindowEvent(delegate.windowId, EventWindowShouldClose); + return false; +} +- (void) dealloc { + // Makes sure to remove the retained properties so the reference counter of the retains are decreased + self.leftMouseEvent = nil; + [super dealloc]; +} +- (void) startDrag:(WebviewWindow*)window { + [window performWindowDragWithEvent:self.leftMouseEvent]; +} +// Handle script messages from the external bridge +- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message { + NSString *m = message.body; + const char *_m = [m UTF8String]; + processMessage(self.windowId, _m); +} +- (void)handleLeftMouseDown:(NSEvent *)event { + self.leftMouseEvent = event; + NSWindow *window = [event window]; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + if( self.invisibleTitleBarHeight > 0 ) { + NSPoint location = [event locationInWindow]; + NSRect frame = [window frame]; + if( location.y > frame.size.height - self.invisibleTitleBarHeight ) { + [window performWindowDragWithEvent:event]; + return; + } + } +} +- (void)handleLeftMouseUp:(NSWindow *)window { + self.leftMouseEvent = nil; +} +- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { + processURLRequest(self.windowId, urlSchemeTask); +} +- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (stream) { + NSStreamStatus status = stream.streamStatus; + if (status != NSStreamStatusClosed && status != NSStreamStatusNotOpen) { + [stream close]; + } + } +} +- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions { + if (self.showToolbarWhenFullscreen) { + return proposedOptions; + } else { + return proposedOptions | NSApplicationPresentationAutoHideToolbar; + } +} +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { + NSWindow *window = notification.object; + BOOL isVisible = ([window occlusionState] & NSWindowOcclusionStateVisible) != 0; + if (hasListeners(isVisible ? EventWindowShow : EventWindowHide)) { + processWindowEvent(self.windowId, isVisible ? EventWindowShow : EventWindowHide); + } +} +- (void)windowDidBecomeKey:(NSNotification *)notification { + if( hasListeners(EventWindowDidBecomeKey) ) { + processWindowEvent(self.windowId, EventWindowDidBecomeKey); + } +} +- (void)windowDidBecomeMain:(NSNotification *)notification { + if( hasListeners(EventWindowDidBecomeMain) ) { + processWindowEvent(self.windowId, EventWindowDidBecomeMain); + } +} +- (void)windowDidBeginSheet:(NSNotification *)notification { + if( hasListeners(EventWindowDidBeginSheet) ) { + processWindowEvent(self.windowId, EventWindowDidBeginSheet); + } +} +- (void)windowDidChangeAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeAlpha) ) { + processWindowEvent(self.windowId, EventWindowDidChangeAlpha); + } +} +- (void)windowDidChangeBackingLocation:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeBackingLocation) ) { + processWindowEvent(self.windowId, EventWindowDidChangeBackingLocation); + } +} +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeBackingProperties) ) { + processWindowEvent(self.windowId, EventWindowDidChangeBackingProperties); + } +} +- (void)windowDidChangeCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowDidChangeCollectionBehavior); + } +} +- (void)windowDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeEffectiveAppearance) ) { + processWindowEvent(self.windowId, EventWindowDidChangeEffectiveAppearance); + } +} +- (void)windowDidChangeOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowDidChangeOrderingMode); + } +} +- (void)windowDidChangeScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreen) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreen); + } +} +- (void)windowDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenParameters) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenParameters); + } +} +- (void)windowDidChangeScreenProfile:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenProfile) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenProfile); + } +} +- (void)windowDidChangeScreenSpace:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenSpace) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenSpace); + } +} +- (void)windowDidChangeScreenSpaceProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeScreenSpaceProperties) ) { + processWindowEvent(self.windowId, EventWindowDidChangeScreenSpaceProperties); + } +} +- (void)windowDidChangeSharingType:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSharingType) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSharingType); + } +} +- (void)windowDidChangeSpace:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSpace) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSpace); + } +} +- (void)windowDidChangeSpaceOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeSpaceOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowDidChangeSpaceOrderingMode); + } +} +- (void)windowDidChangeTitle:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeTitle) ) { + processWindowEvent(self.windowId, EventWindowDidChangeTitle); + } +} +- (void)windowDidChangeToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeToolbar) ) { + processWindowEvent(self.windowId, EventWindowDidChangeToolbar); + } +} +- (void)windowDidDeminiaturize:(NSNotification *)notification { + if( hasListeners(EventWindowDidDeminiaturize) ) { + processWindowEvent(self.windowId, EventWindowDidDeminiaturize); + } +} +- (void)windowDidEndSheet:(NSNotification *)notification { + if( hasListeners(EventWindowDidEndSheet) ) { + processWindowEvent(self.windowId, EventWindowDidEndSheet); + } +} +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidEnterFullScreen) ) { + processWindowEvent(self.windowId, EventWindowDidEnterFullScreen); + } +} +- (void)windowMaximise:(NSNotification *)notification { + if( hasListeners(EventWindowMaximise) ) { + processWindowEvent(self.windowId, EventWindowMaximise); + } +} +- (void)windowUnMaximise:(NSNotification *)notification { + if( hasListeners(EventWindowUnMaximise) ) { + processWindowEvent(self.windowId, EventWindowUnMaximise); + } +} +- (void)windowDidEnterVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowDidEnterVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowDidEnterVersionBrowser); + } +} +- (void)windowDidExitFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowDidExitFullScreen) ) { + processWindowEvent(self.windowId, EventWindowDidExitFullScreen); + } +} +- (void)windowDidExitVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowDidExitVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowDidExitVersionBrowser); + } +} +- (void)windowDidExpose:(NSNotification *)notification { + if( hasListeners(EventWindowDidExpose) ) { + processWindowEvent(self.windowId, EventWindowDidExpose); + } +} +- (void)windowDidFocus:(NSNotification *)notification { + if( hasListeners(EventWindowDidFocus) ) { + processWindowEvent(self.windowId, EventWindowDidFocus); + } +} +- (void)windowDidMiniaturize:(NSNotification *)notification { + if( hasListeners(EventWindowDidMiniaturize) ) { + processWindowEvent(self.windowId, EventWindowDidMiniaturize); + } +} +- (void)windowDidMove:(NSNotification *)notification { + if( hasListeners(EventWindowDidMove) ) { + processWindowEvent(self.windowId, EventWindowDidMove); + } +} +- (void)windowDidOrderOffScreen:(NSNotification *)notification { + NSLog(@"[DEBUG] Window %d ordered OFF screen (hidden)", self.windowId); + if( hasListeners(EventWindowDidOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowDidOrderOffScreen); + } +} +- (void)windowDidOrderOnScreen:(NSNotification *)notification { + NSLog(@"[DEBUG] Window %d ordered ON screen (shown)", self.windowId); + if( hasListeners(EventWindowDidOrderOnScreen) ) { + processWindowEvent(self.windowId, EventWindowDidOrderOnScreen); + } +} +- (void)windowDidResignKey:(NSNotification *)notification { + if( hasListeners(EventWindowDidResignKey) ) { + processWindowEvent(self.windowId, EventWindowDidResignKey); + } +} +- (void)windowDidResignMain:(NSNotification *)notification { + if( hasListeners(EventWindowDidResignMain) ) { + processWindowEvent(self.windowId, EventWindowDidResignMain); + } +} +- (void)windowDidResize:(NSNotification *)notification { + if( hasListeners(EventWindowDidResize) ) { + processWindowEvent(self.windowId, EventWindowDidResize); + } +} +- (void)windowDidUpdate:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdate) ) { + processWindowEvent(self.windowId, EventWindowDidUpdate); + } +} +- (void)windowDidUpdateAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateAlpha) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateAlpha); + } +} +- (void)windowDidUpdateCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateCollectionBehavior); + } +} +- (void)windowDidUpdateCollectionProperties:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateCollectionProperties) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateCollectionProperties); + } +} +- (void)windowDidUpdateShadow:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateShadow) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateShadow); + } +} +- (void)windowDidUpdateTitle:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateTitle) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateTitle); + } +} +- (void)windowDidUpdateToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateToolbar) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateToolbar); + } +} +- (void)windowWillBecomeKey:(NSNotification *)notification { + if( hasListeners(EventWindowWillBecomeKey) ) { + processWindowEvent(self.windowId, EventWindowWillBecomeKey); + } +} +- (void)windowWillBecomeMain:(NSNotification *)notification { + if( hasListeners(EventWindowWillBecomeMain) ) { + processWindowEvent(self.windowId, EventWindowWillBecomeMain); + } +} +- (void)windowWillBeginSheet:(NSNotification *)notification { + if( hasListeners(EventWindowWillBeginSheet) ) { + processWindowEvent(self.windowId, EventWindowWillBeginSheet); + } +} +- (void)windowWillChangeOrderingMode:(NSNotification *)notification { + if( hasListeners(EventWindowWillChangeOrderingMode) ) { + processWindowEvent(self.windowId, EventWindowWillChangeOrderingMode); + } +} +- (void)windowWillClose:(NSNotification *)notification { + NSLog(@"[DEBUG] Window %d WILL close (window is actually closing)", self.windowId); + if( hasListeners(EventWindowWillClose) ) { + processWindowEvent(self.windowId, EventWindowWillClose); + } +} +- (void)windowWillDeminiaturize:(NSNotification *)notification { + if( hasListeners(EventWindowWillDeminiaturize) ) { + processWindowEvent(self.windowId, EventWindowWillDeminiaturize); + } +} +- (void)windowWillEnterFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillEnterFullScreen) ) { + processWindowEvent(self.windowId, EventWindowWillEnterFullScreen); + } +} +- (void)windowWillEnterVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowWillEnterVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowWillEnterVersionBrowser); + } +} +- (void)windowWillExitFullScreen:(NSNotification *)notification { + if( hasListeners(EventWindowWillExitFullScreen) ) { + processWindowEvent(self.windowId, EventWindowWillExitFullScreen); + } +} +- (void)windowWillExitVersionBrowser:(NSNotification *)notification { + if( hasListeners(EventWindowWillExitVersionBrowser) ) { + processWindowEvent(self.windowId, EventWindowWillExitVersionBrowser); + } +} +- (void)windowWillFocus:(NSNotification *)notification { + if( hasListeners(EventWindowWillFocus) ) { + processWindowEvent(self.windowId, EventWindowWillFocus); + } +} +- (void)windowWillMiniaturize:(NSNotification *)notification { + if( hasListeners(EventWindowWillMiniaturize) ) { + processWindowEvent(self.windowId, EventWindowWillMiniaturize); + } +} +- (void)windowWillMove:(NSNotification *)notification { + if( hasListeners(EventWindowWillMove) ) { + processWindowEvent(self.windowId, EventWindowWillMove); + } +} +- (void)windowWillOrderOffScreen:(NSNotification *)notification { + NSLog(@"[DEBUG] Window %d WILL order off screen (about to hide)", self.windowId); + if( hasListeners(EventWindowWillOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowWillOrderOffScreen); + } +} +- (void)windowWillOrderOnScreen:(NSNotification *)notification { + NSLog(@"[DEBUG] Window %d WILL order on screen (about to show)", self.windowId); + if( hasListeners(EventWindowWillOrderOnScreen) ) { + processWindowEvent(self.windowId, EventWindowWillOrderOnScreen); + } +} +- (void)windowWillResignMain:(NSNotification *)notification { + if( hasListeners(EventWindowWillResignMain) ) { + processWindowEvent(self.windowId, EventWindowWillResignMain); + } +} +- (void)windowWillResize:(NSNotification *)notification { + if( hasListeners(EventWindowWillResize) ) { + processWindowEvent(self.windowId, EventWindowWillResize); + } +} +- (void)windowWillUnfocus:(NSNotification *)notification { + if( hasListeners(EventWindowWillUnfocus) ) { + processWindowEvent(self.windowId, EventWindowWillUnfocus); + } +} +- (void)windowWillUpdate:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdate) ) { + processWindowEvent(self.windowId, EventWindowWillUpdate); + } +} +- (void)windowWillUpdateAlpha:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateAlpha) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateAlpha); + } +} +- (void)windowWillUpdateCollectionBehavior:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateCollectionBehavior) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateCollectionBehavior); + } +} +- (void)windowWillUpdateCollectionProperties:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateCollectionProperties) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateCollectionProperties); + } +} +- (void)windowWillUpdateShadow:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateShadow) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateShadow); + } +} +- (void)windowWillUpdateTitle:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateTitle) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateTitle); + } +} +- (void)windowWillUpdateToolbar:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateToolbar) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateToolbar); + } +} +- (void)windowWillUpdateVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowWillUpdateVisibility) ) { + processWindowEvent(self.windowId, EventWindowWillUpdateVisibility); + } +} +- (void)windowWillUseStandardFrame:(NSNotification *)notification { + if( hasListeners(EventWindowWillUseStandardFrame) ) { + processWindowEvent(self.windowId, EventWindowWillUseStandardFrame); + } +} +- (void)windowFileDraggingEntered:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingEntered) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingEntered); + } +} +- (void)windowFileDraggingPerformed:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingPerformed) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); + } +} +- (void)windowFileDraggingExited:(NSNotification *)notification { + if( hasListeners(EventWindowFileDraggingExited) ) { + processWindowEvent(self.windowId, EventWindowFileDraggingExited); + } +} +- (void)windowShow:(NSNotification *)notification { + if( hasListeners(EventWindowShow) ) { + processWindowEvent(self.windowId, EventWindowShow); + } +} +- (void)windowHide:(NSNotification *)notification { + if( hasListeners(EventWindowHide) ) { + processWindowEvent(self.windowId, EventWindowHide); + } +} +- (void)webView:(nonnull WKWebView *)webview didStartProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidStartProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidStartProvisionalNavigation); + } +} +- (void)webView:(nonnull WKWebView *)webview didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidReceiveServerRedirectForProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidReceiveServerRedirectForProvisionalNavigation); + } +} +- (void)webView:(nonnull WKWebView *)webview didFinishNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidFinishNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidFinishNavigation); + } +} +- (void)webView:(nonnull WKWebView *)webview didCommitNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidCommitNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidCommitNavigation); + } +} +// GENERATED EVENTS END +@end +void windowSetScreen(void* window, void* screen, int yOffset) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSScreen* nsScreen = (NSScreen*)screen; + + // Get current frame + NSRect frame = [nsWindow frame]; + + // Convert frame to screen coordinates + NSRect screenFrame = [nsScreen frame]; + NSRect currentScreenFrame = [[nsWindow screen] frame]; + + // Calculate the menubar height for the target screen + NSRect visibleFrame = [nsScreen visibleFrame]; + CGFloat menubarHeight = screenFrame.size.height - visibleFrame.size.height; + + // Calculate the distance from the top of the current screen + CGFloat topOffset = currentScreenFrame.origin.y + currentScreenFrame.size.height - frame.origin.y; + + // Position relative to new screen's top, accounting for menubar + frame.origin.x = screenFrame.origin.x + (frame.origin.x - currentScreenFrame.origin.x); + frame.origin.y = screenFrame.origin.y + screenFrame.size.height - topOffset - menubarHeight - yOffset; + + // Set the frame which moves the window to the new screen + [nsWindow setFrame:frame display:YES]; +} diff --git a/v3/pkg/application/webview_window_darwin_dev.go b/v3/pkg/application/webview_window_darwin_dev.go new file mode 100644 index 000000000..6ae5fa998 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_dev.go @@ -0,0 +1,57 @@ +//go:build darwin && (!production || devtools) + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#import + +#include "webview_window_darwin.h" + +@interface _WKInspector : NSObject +- (void)show; +- (void)detach; +@end + +@interface WKWebView () +- (_WKInspector *)_inspector; +@end + +void openDevTools(void *window) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 + if (@available(macOS 12.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^{ + WebviewWindow* nsWindow = (WebviewWindow*)window; + + @try { + [nsWindow.webView._inspector show]; + } @catch (NSException *exception) { + NSLog(@"Opening the inspector failed: %@", exception.reason); + return; + } + }); + } +#else + NSLog(@"Opening the inspector needs at least MacOS 12"); +#endif +} + +// Enable NSWindow devtools +void windowEnableDevTools(void* nsWindow) { + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Enable devtools in webview + [window.webView.configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"]; +} + +*/ +import "C" + +func (w *macosWebviewWindow) openDevTools() { + C.openDevTools(w.nsWindow) +} + +func (w *macosWebviewWindow) enableDevTools() { + C.windowEnableDevTools(w.nsWindow) +} diff --git a/v3/pkg/application/webview_window_darwin_drag.h b/v3/pkg/application/webview_window_darwin_drag.h new file mode 100644 index 000000000..aca00d970 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_drag.h @@ -0,0 +1,7 @@ +//go:build darwin + +#import + +@interface WebviewDrag : NSView +@property unsigned int windowId; +@end \ No newline at end of file diff --git a/v3/pkg/application/webview_window_darwin_drag.m b/v3/pkg/application/webview_window_darwin_drag.m new file mode 100644 index 000000000..9a8e9c574 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_drag.m @@ -0,0 +1,60 @@ +//go:build darwin + +#import +#import +#import "webview_window_darwin_drag.h" + +#import "../events/events_darwin.h" + +extern void processDragItems(unsigned int windowId, char** arr, int length); + +@implementation WebviewDrag + +- (instancetype)initWithFrame:(NSRect)frameRect { + self = [super initWithFrame:frameRect]; + if (self) { + [self registerForDraggedTypes:@[NSFilenamesPboardType]]; + } + + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + NSPasteboard *pasteboard = [sender draggingPasteboard]; + if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { + processWindowEvent(self.windowId, EventWindowFileDraggingEntered); + return NSDragOperationCopy; + } + return NSDragOperationNone; +} + + +- (void)draggingExited:(id)sender { + processWindowEvent(self.windowId, EventWindowFileDraggingExited); +} + +- (BOOL)prepareForDragOperation:(id)sender { + return YES; +} + +- (BOOL)performDragOperation:(id)sender { + NSPasteboard *pasteboard = [sender draggingPasteboard]; + processWindowEvent(self.windowId, EventWindowFileDraggingPerformed); + if ([[pasteboard types] containsObject:NSFilenamesPboardType]) { + NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; + NSUInteger count = [files count]; + char** cArray = (char**)malloc(count * sizeof(char*)); + for (NSUInteger i = 0; i < count; i++) { + NSString* str = files[i]; + cArray[i] = (char*)[str UTF8String]; + } + processDragItems(self.windowId, cArray, (int)count); + free(cArray); + return YES; + } + return NO; +} + + +@end + diff --git a/v3/pkg/application/webview_window_darwin_production.go b/v3/pkg/application/webview_window_darwin_production.go new file mode 100644 index 000000000..96e2de74c --- /dev/null +++ b/v3/pkg/application/webview_window_darwin_production.go @@ -0,0 +1,6 @@ +//go:build darwin && production && !devtools + +package application + +func (w *macosWebviewWindow) enableDevTools() {} +func (w *macosWebviewWindow) openDevTools() {} diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go new file mode 100644 index 000000000..acf8a47c9 --- /dev/null +++ b/v3/pkg/application/webview_window_linux.go @@ -0,0 +1,429 @@ +//go:build linux + +package application + +import ( + "fmt" + "time" + + "github.com/bep/debounce" + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/capabilities" + "github.com/wailsapp/wails/v3/internal/runtime" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type dragInfo struct { + XRoot int + YRoot int + DragTime uint32 + MouseButton uint +} + +type linuxWebviewWindow struct { + id uint + application pointer + window pointer + webview pointer + parent *WebviewWindow + menubar pointer + vbox pointer + accels pointer + lastWidth int + lastHeight int + drag dragInfo + lastX, lastY int + gtkmenu pointer + ctxMenuOpened bool + + moveDebouncer func(func()) + resizeDebouncer func(func()) + ignoreMouseEvents bool +} + +var ( + registered bool = false // avoid 'already registered message' about 'wails://' +) + +func (w *linuxWebviewWindow) endDrag(button uint, x, y int) { + w.drag.XRoot = 0.0 + w.drag.YRoot = 0.0 + w.drag.DragTime = 0 +} + +func (w *linuxWebviewWindow) connectSignals() { + cb := func(e events.WindowEventType) { + w.parent.emit(e) + } + w.setupSignalHandlers(cb) +} + +func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { + // Create the menu manually because we don't want a gtk_menu_bar + // as the top-level item + ctxMenu := &linuxMenu{ + menu: menu, + } + if menu.impl == nil { + ctxMenu.update() + + native := ctxMenu.menu.impl.(*linuxMenu).native + w.contextMenuSignals(native) + } + + native := ctxMenu.menu.impl.(*linuxMenu).native + w.contextMenuShow(native, data) +} + +func (w *linuxWebviewWindow) focus() { + w.present() +} + +func (w *linuxWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *linuxWebviewWindow) setCloseButtonEnabled(enabled bool) { + // C.enableCloseButton(w.nsWindow, C.bool(enabled)) +} + +func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) { + // Not implemented +} + +func (w *linuxWebviewWindow) setMinimiseButtonEnabled(enabled bool) { + //C.enableMinimiseButton(w.nsWindow, C.bool(enabled)) +} + +func (w *linuxWebviewWindow) setMaximiseButtonEnabled(enabled bool) { + //C.enableMaximiseButton(w.nsWindow, C.bool(enabled)) +} + +func (w *linuxWebviewWindow) disableSizeConstraints() { + x, y, width, height, scaleFactor := w.getCurrentMonitorGeometry() + w.setMinMaxSize(x, y, width*scaleFactor, height*scaleFactor) +} + +func (w *linuxWebviewWindow) unminimise() { + w.present() +} + +func (w *linuxWebviewWindow) on(eventID uint) { + // TODO: Test register/unregister listener for linux events + //C.registerListener(C.uint(eventID)) +} + +func (w *linuxWebviewWindow) zoom() { + w.zoomIn() +} + +func (w *linuxWebviewWindow) windowZoom() { + w.zoom() // FIXME> This should be removed +} + +func (w *linuxWebviewWindow) forceReload() { + w.reload() +} + +func (w *linuxWebviewWindow) center() { + x, y, width, height, _ := w.getCurrentMonitorGeometry() + if x == -1 && y == -1 && width == -1 && height == -1 { + return + } + windowWidth, windowHeight := w.size() + + newX := ((width - windowWidth) / 2) + x + newY := ((height - windowHeight) / 2) + y + + // Place the window at the center of the monitor + w.move(newX, newY) +} + +func (w *linuxWebviewWindow) restore() { + // restore window to normal size + // FIXME: never called! - remove from webviewImpl interface +} + +func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow { + // (*C.struct__GtkWidget)(m.native) + //var menubar *C.struct__GtkWidget + result := &linuxWebviewWindow{ + application: getNativeApplication().application, + parent: parent, + // menubar: menubar, + } + return result +} + +func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) { + // Get current screen for window + _, _, monitorwidth, monitorheight, _ := w.getCurrentMonitorGeometry() + if monitorwidth == -1 { + monitorwidth = 1920 + } + if monitorheight == -1 { + monitorheight = 1080 + } + if maxWidth == 0 { + maxWidth = monitorwidth + } + if maxHeight == 0 { + maxHeight = monitorheight + } + windowSetGeometryHints(w.window, minWidth, minHeight, maxWidth, maxHeight) +} + +func (w *linuxWebviewWindow) setMinSize(width, height int) { + w.setMinMaxSize(width, height, w.parent.options.MaxWidth, w.parent.options.MaxHeight) +} + +func (w *linuxWebviewWindow) getBorderSizes() *LRTB { + return &LRTB{} +} + +func (w *linuxWebviewWindow) setMaxSize(width, height int) { + w.setMinMaxSize(w.parent.options.MinWidth, w.parent.options.MinHeight, width, height) +} + +func (w *linuxWebviewWindow) setRelativePosition(x, y int) { + mx, my, _, _, _ := w.getCurrentMonitorGeometry() + w.move(x+mx, y+my) +} + +func (w *linuxWebviewWindow) width() int { + width, _ := w.size() + return width +} + +func (w *linuxWebviewWindow) height() int { + _, height := w.size() + return height +} + +func (w *linuxWebviewWindow) setPosition(x int, y int) { + // Set the window's absolute position + w.move(x, y) +} + +func (w *linuxWebviewWindow) bounds() Rect { + // DOTO: do it in a single step + proper DPI scaling + x, y := w.position() + width, height := w.size() + + return Rect{ + X: x, + Y: y, + Width: width, + Height: height, + } +} + +func (w *linuxWebviewWindow) setBounds(bounds Rect) { + // DOTO: do it in a single step + proper DPI scaling + w.move(bounds.X, bounds.Y) + w.setSize(bounds.Width, bounds.Height) + +} + +func (w *linuxWebviewWindow) physicalBounds() Rect { + // TODO: proper DPI scaling + return w.bounds() +} + +func (w *linuxWebviewWindow) setPhysicalBounds(physicalBounds Rect) { + // TODO: proper DPI scaling + w.setBounds(physicalBounds) +} + +func (w *linuxWebviewWindow) setMenu(menu *Menu) { + if menu == nil { + w.gtkmenu = nil + return + } + w.parent.options.Linux.Menu = menu + w.gtkmenu = (menu.impl).(*linuxMenu).native +} + +func (w *linuxWebviewWindow) run() { + for eventId := range w.parent.eventListeners { + w.on(eventId) + } + + if w.moveDebouncer == nil { + debounceMS := w.parent.options.Linux.WindowDidMoveDebounceMS + if debounceMS == 0 { + debounceMS = 50 // Default value + } + w.moveDebouncer = debounce.New(time.Duration(debounceMS) * time.Millisecond) + } + if w.resizeDebouncer == nil { + debounceMS := w.parent.options.Linux.WindowDidMoveDebounceMS + if debounceMS == 0 { + debounceMS = 50 // Default value + } + w.resizeDebouncer = debounce.New(time.Duration(debounceMS) * time.Millisecond) + } + + // Register the capabilities + globalApplication.capabilities = capabilities.NewCapabilities() + + app := getNativeApplication() + + var menu = w.parent.options.Linux.Menu + if menu != nil { + InvokeSync(func() { + menu.Update() + }) + w.gtkmenu = (menu.impl).(*linuxMenu).native + } + + w.window, w.webview, w.vbox = windowNew(app.application, w.gtkmenu, w.parent.id, w.parent.options.Linux.WebviewGpuPolicy) + app.registerWindow(w.window, w.parent.id) // record our mapping + w.connectSignals() + if w.parent.options.EnableDragAndDrop { + w.enableDND() + } + w.setTitle(w.parent.options.Title) + w.setIcon(app.icon) + w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) + w.setResizable(!w.parent.options.DisableResize) + // Set min/max size with defaults + // Default min: 1x1 (smallest possible) + // Default max: 0x0 (uses screen size) + minWidth := w.parent.options.MinWidth + if minWidth == 0 { + minWidth = 1 + } + minHeight := w.parent.options.MinHeight + if minHeight == 0 { + minHeight = 1 + } + maxWidth := w.parent.options.MaxWidth + maxHeight := w.parent.options.MaxHeight + + w.setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight) + w.setDefaultSize(w.parent.options.Width, w.parent.options.Height) + w.setSize(w.parent.options.Width, w.parent.options.Height) + w.setZoom(w.parent.options.Zoom) + if w.parent.options.BackgroundType != BackgroundTypeSolid { + w.setTransparent() + w.setBackgroundColour(w.parent.options.BackgroundColour) + } + + w.setFrameless(w.parent.options.Frameless) + + if w.parent.options.InitialPosition == WindowCentered { + w.center() + } else { + w.setPosition(w.parent.options.X, w.parent.options.Y) + } + + switch w.parent.options.StartState { + case WindowStateMaximised: + w.maximise() + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + case WindowStateNormal: + } + + // Ignore mouse events if requested + w.setIgnoreMouseEvents(w.parent.options.IgnoreMouseEvents) + + startURL, err := assetserver.GetStartURL(w.parent.options.URL) + if err != nil { + globalApplication.handleFatalError(err) + } + + w.setURL(startURL) + w.parent.OnWindowEvent(events.Linux.WindowLoadChanged, func(_ *WindowEvent) { + InvokeAsync(func() { + if w.parent.options.JS != "" { + w.execJS(w.parent.options.JS) + } + if w.parent.options.CSS != "" { + js := fmt.Sprintf("(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%s')); document.head.appendChild(style); })();", w.parent.options.CSS) + w.execJS(js) + } + }) + }) + + w.parent.RegisterHook(events.Linux.WindowLoadChanged, func(e *WindowEvent) { + w.execJS(runtime.Core()) + }) + if w.parent.options.HTML != "" { + w.setHTML(w.parent.options.HTML) + } + if !w.parent.options.Hidden { + w.show() + if w.parent.options.InitialPosition == WindowCentered { + w.center() + } else { + w.setRelativePosition(w.parent.options.X, w.parent.options.Y) + } + } + if w.parent.options.DevToolsEnabled || globalApplication.isDebugMode { + w.enableDevTools() + if w.parent.options.OpenInspectorOnStartup { + w.openDevTools() + } + } +} + +func (w *linuxWebviewWindow) startResize(border string) error { + // FIXME: what do we need to do here? + return nil +} + +func (w *linuxWebviewWindow) nativeWindowHandle() uintptr { + return uintptr(w.window) +} + +func (w *linuxWebviewWindow) print() error { + w.execJS("window.print();") + return nil +} + +func (w *linuxWebviewWindow) handleKeyEvent(acceleratorString string) { + // Parse acceleratorString + // accelerator, err := parseAccelerator(acceleratorString) + // if err != nil { + // globalApplication.error("unable to parse accelerator: %w", err) + // return + // } + w.parent.processKeyBinding(acceleratorString) +} + +// SetMinimiseButtonState is unsupported on Linux +func (w *linuxWebviewWindow) setMinimiseButtonState(state ButtonState) {} + +// SetMaximiseButtonState is unsupported on Linux +func (w *linuxWebviewWindow) setMaximiseButtonState(state ButtonState) {} + +// SetCloseButtonState is unsupported on Linux +func (w *linuxWebviewWindow) setCloseButtonState(state ButtonState) {} + +func (w *linuxWebviewWindow) isIgnoreMouseEvents() bool { + return w.ignoreMouseEvents +} + +func (w *linuxWebviewWindow) setIgnoreMouseEvents(ignore bool) { + w.ignoreMouse(w.ignoreMouseEvents) +} + +func (w *linuxWebviewWindow) show() { + // Linux implementation is robust - window shows immediately + // This is the preferred pattern that Windows should follow + w.windowShow() +} + +func (w *linuxWebviewWindow) hide() { + // Save position before hiding (consistent with CGO implementation) + w.lastX, w.lastY = w.position() + w.windowHide() +} + +func (w *linuxWebviewWindow) showMenuBar() {} +func (w *linuxWebviewWindow) hideMenuBar() {} +func (w *linuxWebviewWindow) toggleMenuBar() {} diff --git a/v3/pkg/application/webview_window_linux_dev.go b/v3/pkg/application/webview_window_linux_dev.go new file mode 100644 index 000000000..3f34a6e64 --- /dev/null +++ b/v3/pkg/application/webview_window_linux_dev.go @@ -0,0 +1,11 @@ +//go:build linux && !production + +package application + +func (w *linuxWebviewWindow) openDevTools() { + openDevTools(w.webview) +} + +func (w *linuxWebviewWindow) enableDevTools() { + enableDevTools(w.webview) +} diff --git a/v3/pkg/application/webview_window_linux_production.go b/v3/pkg/application/webview_window_linux_production.go new file mode 100644 index 000000000..c65520b8e --- /dev/null +++ b/v3/pkg/application/webview_window_linux_production.go @@ -0,0 +1,7 @@ +//go:build linux && production && !devtools + +package application + +func (w *linuxWebviewWindow) openDevTools() {} + +func (w *linuxWebviewWindow) enableDevTools() {} diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go new file mode 100644 index 000000000..712adf4ad --- /dev/null +++ b/v3/pkg/application/webview_window_options.go @@ -0,0 +1,574 @@ +package application + +import ( + "github.com/leaanthony/u" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type WindowState int + +const ( + WindowStateNormal WindowState = iota + WindowStateMinimised + WindowStateMaximised + WindowStateFullscreen +) + +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) + +type WindowStartPosition int + +const ( + WindowCentered WindowStartPosition = 0 + WindowXY WindowStartPosition = 1 +) + +type WebviewWindowOptions struct { + // Name is a unique identifier that can be given to a window. + Name string + + // Title is the title of the window. + Title string + + // Width is the starting width of the window. + Width int + + // Height is the starting height of the window. + Height int + + // AlwaysOnTop will make the window float above other windows. + AlwaysOnTop bool + + // URL is the URL to load in the window. + URL string + + // DisableResize will disable the ability to resize the window. + DisableResize bool + + // Frameless will remove the window frame. + Frameless bool + + // MinWidth is the minimum width of the window. + MinWidth int + + // MinHeight is the minimum height of the window. + MinHeight int + + // MaxWidth is the maximum width of the window. + MaxWidth int + + // MaxHeight is the maximum height of the window. + MaxHeight int + + // StartState indicates the state of the window when it is first shown. + // Default: WindowStateNormal + StartState WindowState + + // BackgroundType is the type of background to use for the window. + // Default: BackgroundTypeSolid + BackgroundType BackgroundType + + // BackgroundColour is the colour to use for the window background. + BackgroundColour RGBA + + // HTML is the HTML to load in the window. + HTML string + + // JS is the JavaScript to load in the window. + JS string + + // CSS is the CSS to load in the window. + CSS string + + // Initial Position + InitialPosition WindowStartPosition + + // X is the starting X position of the window. + X int + + // Y is the starting Y position of the window. + Y int + + // Hidden will hide the window when it is first created. + Hidden bool + + // Zoom is the zoom level of the window. + Zoom float64 + + // ZoomControlEnabled will enable the zoom control. + ZoomControlEnabled bool + + // EnableDragAndDrop will enable drag and drop. + EnableDragAndDrop bool + + // OpenInspectorOnStartup will open the inspector when the window is first shown. + OpenInspectorOnStartup bool + + // Mac options + Mac MacWindow + + // Windows options + Windows WindowsWindow + + // Linux options + Linux LinuxWindow + + // Toolbar button states + MinimiseButtonState ButtonState + MaximiseButtonState ButtonState + CloseButtonState ButtonState + + // If true, the window's devtools will be available (default true in builds without the `production` build tag) + DevToolsEnabled bool + + // If true, the window's default context menu will be disabled (default false) + DefaultContextMenuDisabled bool + + // KeyBindings is a map of key bindings to functions + KeyBindings map[string]func(window *WebviewWindow) + + // IgnoreMouseEvents will ignore mouse events in the window (Windows + Mac only) + IgnoreMouseEvents bool +} + +type RGBA struct { + Red, Green, Blue, Alpha uint8 +} + +func NewRGBA(red, green, blue, alpha uint8) RGBA { + return RGBA{ + Red: red, + Green: green, + Blue: blue, + Alpha: alpha, + } +} + +func NewRGB(red, green, blue uint8) RGBA { + return RGBA{ + Red: red, + Green: green, + Blue: blue, + Alpha: 255, + } +} + +func NewRGBPtr(red, green, blue uint8) *uint32 { + result := uint32(red) + result |= uint32(green) << 8 + result |= uint32(blue) << 16 + return &result +} + +type BackgroundType int + +const ( + BackgroundTypeSolid BackgroundType = iota + BackgroundTypeTransparent + BackgroundTypeTranslucent +) + +/******* Windows Options *******/ + +type BackdropType int32 +type DragEffect int32 + +const ( + // DragEffectNone is used to indicate that the drop target cannot accept the data. + DragEffectNone DragEffect = 1 + // DragEffectCopy is used to indicate that the data is copied to the drop target. + DragEffectCopy DragEffect = 2 + // DragEffectMove is used to indicate that the data is removed from the drag source. + DragEffectMove DragEffect = 3 + // DragEffectLink is used to indicate that a link to the original data is established. + DragEffectLink DragEffect = 4 + // DragEffectScroll is used to indicate that the target can be scrolled while dragging to locate a drop position that is not currently visible in the target. + +) + +const ( + Auto BackdropType = 0 + None BackdropType = 1 + Mica BackdropType = 2 + Acrylic BackdropType = 3 + Tabbed BackdropType = 4 +) + +type CoreWebView2PermissionKind uint32 + +const ( + CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota + CoreWebView2PermissionKindMicrophone + CoreWebView2PermissionKindCamera + CoreWebView2PermissionKindGeolocation + CoreWebView2PermissionKindNotifications + CoreWebView2PermissionKindOtherSensors + CoreWebView2PermissionKindClipboardRead +) + +type CoreWebView2PermissionState uint32 + +const ( + CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota + CoreWebView2PermissionStateAllow + CoreWebView2PermissionStateDeny +) + +type WindowsWindow struct { + // Select the type of translucent backdrop. Requires Windows 11 22621 or later. + // Only used when window's `BackgroundType` is set to `BackgroundTypeTranslucent`. + // Default: Auto + BackdropType BackdropType + + // Disable the icon in the titlebar + // Default: false + DisableIcon bool + + // Theme (Dark / Light / SystemDefault) + // Default: SystemDefault - The application will follow system theme changes. + Theme Theme + + // Specify custom colours to use for dark/light mode + // Default: nil + CustomTheme ThemeSettings + + // Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown. + // "Rounded Corners" are only available on Windows 11. + // Default: false + DisableFramelessWindowDecorations bool + + // WindowMask is used to set the window shape. Use a PNG with an alpha channel to create a custom shape. + // Default: nil + WindowMask []byte + + // WindowMaskDraggable is used to make the window draggable by clicking on the window mask. + // Default: false + WindowMaskDraggable bool + + // ResizeDebounceMS is the amount of time to debounce redraws of webview2 + // when resizing the window + // Default: 0 + ResizeDebounceMS uint16 + + // WindowDidMoveDebounceMS is the amount of time to debounce the WindowDidMove event + // when moving the window + // Default: 0 + WindowDidMoveDebounceMS uint16 + + // Event mapping for the window. This allows you to define a translation from one event to another. + // Default: nil + EventMapping map[events.WindowEventType]events.WindowEventType + + // HiddenOnTaskbar hides the window from the taskbar + // Default: false + HiddenOnTaskbar bool + + // EnableSwipeGestures enables swipe gestures for the window + // Default: false + EnableSwipeGestures bool + + // Menu is the menu to use for the window. + Menu *Menu + + // Drag Cursor Effects + OnEnterEffect DragEffect + OnOverEffect DragEffect + + // Permissions map for WebView2. If empty, default permissions will be granted. + Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState + + // ExStyle is the extended window style + ExStyle int + + // GeneralAutofillEnabled enables general autofill + GeneralAutofillEnabled bool + + // PasswordAutosaveEnabled enables autosaving passwords + PasswordAutosaveEnabled bool + + // EnabledFeatures and DisabledFeatures are used to enable or disable specific features in the WebView2 browser. + // Available flags: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/webview-features-flags?tabs=dotnetcsharp#available-webview2-browser-flags + // WARNING: Apps in production shouldn't use WebView2 browser flags, + // because these flags might be removed or altered at any time, + // and aren't necessarily supported long-term. + EnabledFeatures []string + DisabledFeatures []string +} + +type Theme int + +const ( + // SystemDefault will use whatever the system theme is. The application will follow system theme changes. + SystemDefault Theme = 0 + // Dark Mode + Dark Theme = 1 + // Light Mode + Light Theme = 2 +) + +type WindowTheme struct { + // BorderColour is the colour of the window border + BorderColour *uint32 + + // TitleBarColour is the colour of the window title bar + TitleBarColour *uint32 + + // TitleTextColour is the colour of the window title text + TitleTextColour *uint32 +} + +type TextTheme struct { + // Text is the colour of the text + Text *uint32 + + // Background is the background colour of the text + Background *uint32 +} + +type MenuBarTheme struct { + // Default is the default theme + Default *TextTheme + + // Hover defines the theme to use when the menu item is hovered + Hover *TextTheme + + // Selected defines the theme to use when the menu item is selected + Selected *TextTheme +} + +// ThemeSettings defines custom colours to use in dark or light mode. +// They may be set using the hex values: 0x00BBGGRR +type ThemeSettings struct { + // Dark mode active window + DarkModeActive *WindowTheme + + // Dark mode inactive window + DarkModeInactive *WindowTheme + + // Light mode active window + LightModeActive *WindowTheme + + // Light mode inactive window + LightModeInactive *WindowTheme + + // Dark mode MenuBar + DarkModeMenuBar *MenuBarTheme + + // Light mode MenuBar + LightModeMenuBar *MenuBarTheme +} + +/****** Mac Options *******/ + +// MacBackdrop is the backdrop type for macOS +type MacBackdrop int + +const ( + // MacBackdropNormal - The default value. The window will have a normal opaque background. + MacBackdropNormal MacBackdrop = iota + // MacBackdropTransparent - The window will have a transparent background, with the content underneath it being visible + MacBackdropTransparent + // MacBackdropTranslucent - The window will have a translucent background, with the content underneath it being "fuzzy" or "frosted" + MacBackdropTranslucent +) + +// MacToolbarStyle is the style of toolbar for macOS +type MacToolbarStyle int + +const ( + // MacToolbarStyleAutomatic - The default value. The style will be determined by the window's given configuration + MacToolbarStyleAutomatic MacToolbarStyle = iota + // MacToolbarStyleExpanded - The toolbar will appear below the window title + MacToolbarStyleExpanded + // MacToolbarStylePreference - The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible + MacToolbarStylePreference + // MacToolbarStyleUnified - The window title will appear inline with the toolbar when visible + MacToolbarStyleUnified + // MacToolbarStyleUnifiedCompact - Same as MacToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window + MacToolbarStyleUnifiedCompact +) + +// MacWindow contains macOS specific options for Webview Windows +type MacWindow struct { + // Backdrop is the backdrop type for the window + Backdrop MacBackdrop + // DisableShadow will disable the window shadow + DisableShadow bool + // TitleBar contains options for the Mac titlebar + TitleBar MacTitleBar + // Appearance is the appearance type for the window + Appearance MacAppearanceType + // InvisibleTitleBarHeight defines the height of an invisible titlebar which responds to dragging + InvisibleTitleBarHeight int + // Maps events from platform specific to common event types + EventMapping map[events.WindowEventType]events.WindowEventType + + // EnableFraudulentWebsiteWarnings will enable warnings for fraudulent websites. + // Default: false + EnableFraudulentWebsiteWarnings bool + + // WebviewPreferences contains preferences for the webview + WebviewPreferences MacWebviewPreferences + + // WindowLevel sets the window level to control the order of windows in the screen + WindowLevel MacWindowLevel +} + +type MacWindowLevel string + +const ( + MacWindowLevelNormal MacWindowLevel = "normal" + MacWindowLevelFloating MacWindowLevel = "floating" + MacWindowLevelTornOffMenu MacWindowLevel = "tornOffMenu" + MacWindowLevelModalPanel MacWindowLevel = "modalPanel" + MacWindowLevelMainMenu MacWindowLevel = "mainMenu" + MacWindowLevelStatus MacWindowLevel = "status" + MacWindowLevelPopUpMenu MacWindowLevel = "popUpMenu" + MacWindowLevelScreenSaver MacWindowLevel = "screenSaver" +) + +// MacWebviewPreferences contains preferences for the Mac webview +type MacWebviewPreferences struct { + // TabFocusesLinks will enable tabbing to links + TabFocusesLinks u.Bool + // TextInteractionEnabled will enable text interaction + TextInteractionEnabled u.Bool + // FullscreenEnabled will enable fullscreen + FullscreenEnabled u.Bool +} + +// MacTitleBar contains options for the Mac titlebar +type MacTitleBar struct { + // AppearsTransparent will make the titlebar transparent + AppearsTransparent bool + // Hide will hide the titlebar + Hide bool + // HideTitle will hide the title + HideTitle bool + // FullSizeContent will extend the window content to the full size of the window + FullSizeContent bool + // UseToolbar will use a toolbar instead of a titlebar + UseToolbar bool + // HideToolbarSeparator will hide the toolbar separator + HideToolbarSeparator bool + // ShowToolbarWhenFullscreen will keep the toolbar visible when the window is in fullscreen mode + ShowToolbarWhenFullscreen bool + // ToolbarStyle is the style of toolbar to use + ToolbarStyle MacToolbarStyle +} + +// MacTitleBarDefault results in the default Mac MacTitleBar +var MacTitleBarDefault = MacTitleBar{ + AppearsTransparent: false, + Hide: false, + HideTitle: false, + FullSizeContent: false, + UseToolbar: false, + HideToolbarSeparator: false, +} + +// Credit: Comments from Electron site + +// MacTitleBarHidden results in a hidden title bar and a full size content window, +// yet the title bar still has the standard window controls (โ€œtraffic lightsโ€) +// in the top left. +var MacTitleBarHidden = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: false, + HideToolbarSeparator: false, +} + +// MacTitleBarHiddenInset results in a hidden title bar with an alternative look where +// the traffic light buttons are slightly more inset from the window edge. +var MacTitleBarHiddenInset = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, +} + +// MacTitleBarHiddenInsetUnified results in a hidden title bar with an alternative look where +// the traffic light buttons are even more inset from the window edge. +var MacTitleBarHiddenInsetUnified = MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, + ToolbarStyle: MacToolbarStyleUnified, +} + +// MacAppearanceType is a type of Appearance for Cocoa windows +type MacAppearanceType string + +const ( + // DefaultAppearance uses the default system value + DefaultAppearance MacAppearanceType = "" + // NSAppearanceNameAqua - The standard light system appearance. + NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua" + // NSAppearanceNameDarkAqua - The standard dark system appearance. + NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua" + // NSAppearanceNameVibrantLight - The light vibrant appearance + NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight" + // NSAppearanceNameAccessibilityHighContrastAqua - A high-contrast version of the standard light system appearance. + NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua" + // NSAppearanceNameAccessibilityHighContrastDarkAqua - A high-contrast version of the standard dark system appearance. + NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua" + // NSAppearanceNameAccessibilityHighContrastVibrantLight - A high-contrast version of the light vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight" + // NSAppearanceNameAccessibilityHighContrastVibrantDark - A high-contrast version of the dark vibrant appearance. + NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark" +) + +/******** Linux Options ********/ + +// WebviewGpuPolicy values used for determining the webview's hardware acceleration policy. +type WebviewGpuPolicy int + +const ( + // WebviewGpuPolicyAlways Hardware acceleration is always enabled. + WebviewGpuPolicyAlways WebviewGpuPolicy = iota + // WebviewGpuPolicyOnDemand Hardware acceleration is enabled/disabled as request by web contents. + WebviewGpuPolicyOnDemand + // WebviewGpuPolicyNever Hardware acceleration is always disabled. + WebviewGpuPolicyNever +) + +// LinuxWindow specific to Linux windows +type LinuxWindow struct { + // Icon Sets up the icon representing the window. This icon is used when the window is minimized + // (also known as iconified). + Icon []byte + + // WindowIsTranslucent sets the window's background to transparent when enabled. + WindowIsTranslucent bool + + // WebviewGpuPolicy used for determining the hardware acceleration policy for the webview. + // - WebviewGpuPolicyAlways + // - WebviewGpuPolicyOnDemand + // - WebviewGpuPolicyNever + // + // Due to https://github.com/wailsapp/wails/issues/2977, if options.Linux is nil + // in the call to wails.Run(), WebviewGpuPolicy is set by default to WebviewGpuPolicyNever. + // Client code may override this behavior by passing a non-nil Options and set + // WebviewGpuPolicy as needed. + WebviewGpuPolicy WebviewGpuPolicy + + // WindowDidMoveDebounceMS is the debounce time in milliseconds for the WindowDidMove event + WindowDidMoveDebounceMS uint16 + + // Menu is the window's menu + Menu *Menu +} diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go new file mode 100644 index 000000000..a39696943 --- /dev/null +++ b/v3/pkg/application/webview_window_windows.go @@ -0,0 +1,2283 @@ +//go:build windows + +package application + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/bep/debounce" + "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v3/internal/assetserver" + "github.com/wailsapp/wails/v3/internal/assetserver/webview" + "github.com/wailsapp/wails/v3/internal/capabilities" + "github.com/wailsapp/wails/v3/internal/runtime" + + "github.com/samber/lo" + + "github.com/wailsapp/go-webview2/pkg/edge" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +var edgeMap = map[string]uintptr{ + "n-resize": w32.HTTOP, + "ne-resize": w32.HTTOPRIGHT, + "e-resize": w32.HTRIGHT, + "se-resize": w32.HTBOTTOMRIGHT, + "s-resize": w32.HTBOTTOM, + "sw-resize": w32.HTBOTTOMLEFT, + "w-resize": w32.HTLEFT, + "nw-resize": w32.HTTOPLEFT, +} + +type windowsWebviewWindow struct { + windowImpl unsafe.Pointer + parent *WebviewWindow + hwnd w32.HWND + menu *Win32Menu + currentlyOpenContextMenu *Win32Menu + ignoreDPIChangeResizing bool + + // Fullscreen flags + isCurrentlyFullscreen bool + previousWindowStyle uint32 + previousWindowExStyle uint32 + previousWindowPlacement w32.WINDOWPLACEMENT + + // Webview + chromium *edge.Chromium + webviewNavigationCompleted bool + + // Window visibility management - robust fallback for issue #2861 + showRequested bool // Track if show() was called before navigation completed + visibilityTimeout *time.Timer // Timeout to show window if navigation is delayed + windowShown bool // Track if window container has been shown + + // resizeBorder* is the width/height of the resize border in pixels. + resizeBorderWidth int32 + resizeBorderHeight int32 + focusingChromium bool + dropTarget *w32.DropTarget + onceDo sync.Once + + // Window move debouncer + moveDebouncer func(func()) + resizeDebouncer func(func()) + + // isMinimizing indicates whether the window is currently being minimized + // Used to prevent unnecessary redraws during minimize/restore operations + isMinimizing bool + + // menubarTheme is the theme for the menubar + menubarTheme *w32.MenuBarTheme +} + +func (w *windowsWebviewWindow) setMenu(menu *Menu) { + menu.Update() + w.menu = NewApplicationMenu(w, menu) + w.menu.parentWindow = w + w32.SetMenu(w.hwnd, w.menu.menu) + + // Set menu background if theme is active + if w.menubarTheme != nil { + globalApplication.debug("Applying menubar theme in setMenu", "window", w.parent.id) + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + // Force a repaint of the menu area + w32.InvalidateRect(w.hwnd, nil, true) + } else { + globalApplication.debug("No menubar theme to apply in setMenu", "window", w.parent.id) + } + + // Check if using translucent background with Mica - this makes menubars invisible + if w.parent.options.BackgroundType == BackgroundTypeTranslucent && + (w.parent.options.Windows.BackdropType == Mica || + w.parent.options.Windows.BackdropType == Acrylic || + w.parent.options.Windows.BackdropType == Tabbed) { + // Log warning about menubar visibility issue + globalApplication.debug("Warning: Menubars may be invisible when using translucent backgrounds with Mica/Acrylic/Tabbed effects", "window", w.parent.id) + } +} + +func (w *windowsWebviewWindow) cut() { + w.execJS("document.execCommand('cut')") +} + +func (w *windowsWebviewWindow) paste() { + w.execJS(` + (async () => { + try { + // Try to read all available formats + const clipboardItems = await navigator.clipboard.read(); + + for (const clipboardItem of clipboardItems) { + // Check for image types + for (const type of clipboardItem.types) { + if (type.startsWith('image/')) { + const blob = await clipboardItem.getType(type); + const url = URL.createObjectURL(blob); + document.execCommand('insertHTML', false, ''); + return; + } + } + + // If no image found, try text + if (clipboardItem.types.includes('text/plain')) { + const text = await navigator.clipboard.readText(); + document.execCommand('insertText', false, text); + return; + } + } + } catch(err) { + // Fallback to text-only paste if clipboard access fails + try { + const text = await navigator.clipboard.readText(); + document.execCommand('insertText', false, text); + } catch(fallbackErr) { + console.error('Failed to paste:', err, fallbackErr); + } + } + })() + `) +} + +func (w *windowsWebviewWindow) copy() { + w.execJS(` + (async () => { + try { + const selection = window.getSelection(); + if (!selection.rangeCount) return; + + const range = selection.getRangeAt(0); + const container = document.createElement('div'); + container.appendChild(range.cloneContents()); + + // Check if we have any images in the selection + const images = container.getElementsByTagName('img'); + if (images.length > 0) { + // Handle image copy + const img = images[0]; // Take the first image + const response = await fetch(img.src); + const blob = await response.blob(); + await navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob + }) + ]); + } else { + // Handle text copy + const text = selection.toString(); + if (text) { + await navigator.clipboard.writeText(text); + } + } + } catch(err) { + console.error('Failed to copy:', err); + } + })() + `) +} + +func (w *windowsWebviewWindow) selectAll() { + w.execJS("document.execCommand('selectAll')") +} + +func (w *windowsWebviewWindow) undo() { + w.execJS("document.execCommand('undo')") +} + +func (w *windowsWebviewWindow) redo() { + w.execJS("document.execCommand('redo')") +} + +func (w *windowsWebviewWindow) delete() { + w.execJS("document.execCommand('delete')") +} + +func (w *windowsWebviewWindow) handleKeyEvent(_ string) { + // Unused on windows +} + +func (w *windowsWebviewWindow) setEnabled(enabled bool) { + w32.EnableWindow(w.hwnd, enabled) +} + +func (w *windowsWebviewWindow) print() error { + w.execJS("window.print();") + return nil +} + +func (w *windowsWebviewWindow) startResize(border string) error { + if !w32.ReleaseCapture() { + return errors.New("unable to release mouse capture") + } + // Use PostMessage because we don't want to block the caller until resizing has been finished. + w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, edgeMap[border], 0) + return nil +} + +func (w *windowsWebviewWindow) startDrag() error { + if !w32.ReleaseCapture() { + return errors.New("unable to release mouse capture") + } + // Use PostMessage because we don't want to block the caller until dragging has been finished. + w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0) + return nil +} + +func (w *windowsWebviewWindow) nativeWindowHandle() uintptr { + return w.hwnd +} + +func (w *windowsWebviewWindow) setTitle(title string) { + w32.SetWindowText(w.hwnd, title) +} + +func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { + w32.SetWindowPos(w.hwnd, + lo.Ternary(alwaysOnTop, w32.HWND_TOPMOST, w32.HWND_NOTOPMOST), + 0, + 0, + 0, + 0, + uint(w32.SWP_NOMOVE|w32.SWP_NOSIZE)) +} + +func (w *windowsWebviewWindow) setURL(url string) { + // Navigate to the given URL in the webview + w.webviewNavigationCompleted = false + w.chromium.Navigate(url) +} + +func (w *windowsWebviewWindow) setResizable(resizable bool) { + w.setStyle(resizable, w32.WS_THICKFRAME) + w.execJS(fmt.Sprintf("window._wails.setResizable(%v);", resizable)) +} + +func (w *windowsWebviewWindow) setMinSize(width, height int) { + w.parent.options.MinWidth = width + w.parent.options.MinHeight = height +} + +func (w *windowsWebviewWindow) setMaxSize(width, height int) { + w.parent.options.MaxWidth = width + w.parent.options.MaxHeight = height +} + +func (w *windowsWebviewWindow) execJS(js string) { + if w.chromium == nil { + return + } + globalApplication.dispatchOnMainThread(func() { + w.chromium.Eval(js) + }) +} + +func (w *windowsWebviewWindow) setBackgroundColour(color RGBA) { + w32.SetBackgroundColour(w.hwnd, color.Red, color.Green, color.Blue) +} + +func (w *windowsWebviewWindow) framelessWithDecorations() bool { + return w.parent.options.Frameless && !w.parent.options.Windows.DisableFramelessWindowDecorations +} + +func (w *windowsWebviewWindow) run() { + + options := w.parent.options + + // Initialize showRequested based on whether window should be hidden + // Non-hidden windows should be shown by default + w.showRequested = !options.Hidden + + w.chromium = edge.NewChromium() + if globalApplication.options.ErrorHandler != nil { + w.chromium.SetErrorCallback(globalApplication.options.ErrorHandler) + } + + exStyle := w32.WS_EX_CONTROLPARENT + if options.BackgroundType != BackgroundTypeSolid { + if (options.Frameless && options.BackgroundType == BackgroundTypeTransparent) || w.parent.options.IgnoreMouseEvents { + // Always if transparent and frameless + exStyle |= w32.WS_EX_TRANSPARENT | w32.WS_EX_LAYERED + } else { + // Only WS_EX_NOREDIRECTIONBITMAP if not (and not solid) + exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP + } + } + if options.AlwaysOnTop { + exStyle |= w32.WS_EX_TOPMOST + } + // If we're frameless, we need to add the WS_EX_TOOLWINDOW style to hide the window from the taskbar + if options.Windows.HiddenOnTaskbar { + //exStyle |= w32.WS_EX_TOOLWINDOW + exStyle |= w32.WS_EX_NOACTIVATE + } else { + exStyle |= w32.WS_EX_APPWINDOW + } + + if options.Windows.ExStyle != 0 { + exStyle = options.Windows.ExStyle + } + + bounds := Rect{ + X: options.X, + Y: options.Y, + Width: options.Width, + Height: options.Height, + } + initialScreen := ScreenNearestDipRect(bounds) + physicalBounds := initialScreen.dipToPhysicalRect(bounds) + + // Default window position applied by the system + // TODO: provide a way to set (0,0) as an initial position? + if options.X == 0 && options.Y == 0 { + physicalBounds.X = w32.CW_USEDEFAULT + physicalBounds.Y = w32.CW_USEDEFAULT + } + + var appMenu w32.HMENU + + // Process Menu + if !options.Frameless { + userMenu := w.parent.options.Windows.Menu + if userMenu != nil { + userMenu.Update() + w.menu = NewApplicationMenu(w, userMenu) + w.menu.parentWindow = w + appMenu = w.menu.menu + } + } + + var parent w32.HWND + + var style uint = w32.WS_OVERLAPPEDWINDOW + + w.hwnd = w32.CreateWindowEx( + uint(exStyle), + w32.MustStringToUTF16Ptr(globalApplication.options.Windows.WndClass), + w32.MustStringToUTF16Ptr(options.Title), + style, + physicalBounds.X, + physicalBounds.Y, + physicalBounds.Width, + physicalBounds.Height, + parent, + appMenu, + w32.GetModuleHandle(""), + nil) + + if w.hwnd == 0 { + globalApplication.fatal("unable to create window") + } + + // Ensure correct window size in case the scale factor of current screen is different from the initial one. + // This could happen when using the default window position and the window launches on a secondary monitor. + currentScreen, _ := w.getScreen() + if currentScreen.ScaleFactor != initialScreen.ScaleFactor { + w.setSize(options.Width, options.Height) + } + + w.setupChromium() + + if options.Windows.WindowDidMoveDebounceMS == 0 { + options.Windows.WindowDidMoveDebounceMS = 50 + } + w.moveDebouncer = debounce.New(time.Duration(options.Windows.WindowDidMoveDebounceMS) * time.Millisecond) + + if options.Windows.ResizeDebounceMS > 0 { + w.resizeDebouncer = debounce.New(time.Duration(options.Windows.ResizeDebounceMS) * time.Millisecond) + } + + // Initialise the window buttons + w.setMinimiseButtonState(options.MinimiseButtonState) + w.setMaximiseButtonState(options.MaximiseButtonState) + w.setCloseButtonState(options.CloseButtonState) + + // Register the window with the application + getNativeApplication().registerWindow(w) + + w.setResizable(!options.DisableResize) + + w.setIgnoreMouseEvents(options.IgnoreMouseEvents) + + if options.Frameless { + // Inform the application of the frame change this is needed to trigger the WM_NCCALCSIZE event. + // => https://learn.microsoft.com/en-us/windows/win32/dwm/customframe#removing-the-standard-frame + // This is normally done in WM_CREATE but we can't handle that there because that is emitted during CreateWindowEx + // and at that time we can't yet register the window for calling our WndProc method. + // This must be called after setResizable above! + rcClient := w32.GetWindowRect(w.hwnd) + w32.SetWindowPos(w.hwnd, + 0, + int(rcClient.Left), + int(rcClient.Top), + int(rcClient.Right-rcClient.Left), + int(rcClient.Bottom-rcClient.Top), + w32.SWP_FRAMECHANGED) + } + + // Icon + if !options.Windows.DisableIcon { + // App icon ID is 3 + icon, err := NewIconFromResource(w32.GetModuleHandle(""), uint16(3)) + if err != nil { + // Try loading from the given icon + if globalApplication.options.Icon != nil { + icon, _ = w32.CreateLargeHIconFromImage(globalApplication.options.Icon) + } + } + if icon != 0 { + w.setIcon(icon) + } + } else { + w.disableIcon() + } + + // Process the theme + switch options.Windows.Theme { + case SystemDefault: + isDark := w32.IsCurrentlyDarkMode() + if isDark { + w32.AllowDarkModeForWindow(w.hwnd, true) + } + w.updateTheme(isDark) + // Don't initialize default dark theme here if custom theme might be set + // The updateTheme call above will handle both default and custom themes + w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*ApplicationEvent) { + InvokeAsync(func() { + w.updateTheme(w32.IsCurrentlyDarkMode()) + }) + }) + case Light: + w.updateTheme(false) + case Dark: + w32.AllowDarkModeForWindow(w.hwnd, true) + w.updateTheme(true) + // Don't initialize default dark theme here if custom theme might be set + // The updateTheme call above will handle custom themes + } + + switch options.BackgroundType { + case BackgroundTypeSolid: + var col = options.BackgroundColour + w.setBackgroundColour(col) + w.chromium.SetBackgroundColour(col.Red, col.Green, col.Blue, col.Alpha) + case BackgroundTypeTransparent: + w.chromium.SetBackgroundColour(0, 0, 0, 0) + case BackgroundTypeTranslucent: + w.chromium.SetBackgroundColour(0, 0, 0, 0) + w.setBackdropType(options.Windows.BackdropType) + } + + // Process StartState + switch options.StartState { + case WindowStateMaximised: + if w.parent.Resizable() { + w.maximise() + } + case WindowStateMinimised: + w.minimise() + case WindowStateFullscreen: + w.fullscreen() + case WindowStateNormal: + } + + // Process window mask + if options.Windows.WindowMask != nil { + w.setWindowMask(options.Windows.WindowMask) + } + + if options.InitialPosition == WindowCentered { + w.center() + } else { + w.setPosition(options.X, options.Y) + } + + if options.Frameless { + // Trigger a resize to ensure the window is sized correctly + w.chromium.Resize() + } +} + +func (w *windowsWebviewWindow) center() { + w32.CenterWindow(w.hwnd) +} + +func (w *windowsWebviewWindow) disableSizeConstraints() { + w.setMaxSize(0, 0) + w.setMinSize(0, 0) +} + +func (w *windowsWebviewWindow) enableSizeConstraints() { + options := w.parent.options + if options.MinWidth > 0 || options.MinHeight > 0 { + w.setMinSize(options.MinWidth, options.MinHeight) + } + if options.MaxWidth > 0 || options.MaxHeight > 0 { + w.setMaxSize(options.MaxWidth, options.MaxHeight) + } +} + +func (w *windowsWebviewWindow) update() { + w32.UpdateWindow(w.hwnd) +} + +// getBorderSizes returns the extended border size for the window +func (w *windowsWebviewWindow) getBorderSizes() *LRTB { + var result LRTB + var frame w32.RECT + w32.DwmGetWindowAttribute(w.hwnd, w32.DWMWA_EXTENDED_FRAME_BOUNDS, unsafe.Pointer(&frame), unsafe.Sizeof(frame)) + rect := w32.GetWindowRect(w.hwnd) + result.Left = int(frame.Left - rect.Left) + result.Top = int(frame.Top - rect.Top) + result.Right = int(rect.Right - frame.Right) + result.Bottom = int(rect.Bottom - frame.Bottom) + return &result +} + +func (w *windowsWebviewWindow) physicalBounds() Rect { + // var rect w32.RECT + // // Get the extended frame bounds instead of the window rect to offset the invisible borders in Windows 10 + // w32.DwmGetWindowAttribute(w.hwnd, w32.DWMWA_EXTENDED_FRAME_BOUNDS, unsafe.Pointer(&rect), unsafe.Sizeof(rect)) + rect := w32.GetWindowRect(w.hwnd) + return Rect{ + X: int(rect.Left), + Y: int(rect.Top), + Width: int(rect.Right - rect.Left), + Height: int(rect.Bottom - rect.Top), + } +} + +func (w *windowsWebviewWindow) setPhysicalBounds(physicalBounds Rect) { + // // Offset invisible borders + // borderSize := w.getBorderSizes() + // physicalBounds.X -= borderSize.Left + // physicalBounds.Y -= borderSize.Top + // physicalBounds.Width += borderSize.Left + borderSize.Right + // physicalBounds.Height += borderSize.Top + borderSize.Bottom + + // Set flag to ignore resizing the window with DPI change because we already calculated the correct size + // for the target position, this prevents double resizing issue when the window is moved between screens + previousFlag := w.ignoreDPIChangeResizing + w.ignoreDPIChangeResizing = true + w32.SetWindowPos(w.hwnd, 0, physicalBounds.X, physicalBounds.Y, physicalBounds.Width, physicalBounds.Height, w32.SWP_NOZORDER|w32.SWP_NOACTIVATE) + w.ignoreDPIChangeResizing = previousFlag +} + +// Get window dip bounds +func (w *windowsWebviewWindow) bounds() Rect { + return PhysicalToDipRect(w.physicalBounds()) +} + +// Set window dip bounds +func (w *windowsWebviewWindow) setBounds(bounds Rect) { + w.setPhysicalBounds(DipToPhysicalRect(bounds)) +} + +func (w *windowsWebviewWindow) size() (int, int) { + bounds := w.bounds() + return bounds.Width, bounds.Height +} + +func (w *windowsWebviewWindow) width() int { + return w.bounds().Width +} + +func (w *windowsWebviewWindow) height() int { + return w.bounds().Height +} + +func (w *windowsWebviewWindow) setSize(width, height int) { + bounds := w.bounds() + bounds.Width = width + bounds.Height = height + + w.setBounds(bounds) +} + +func (w *windowsWebviewWindow) position() (int, int) { + bounds := w.bounds() + return bounds.X, bounds.Y +} + +func (w *windowsWebviewWindow) setPosition(x int, y int) { + bounds := w.bounds() + bounds.X = x + bounds.Y = y + + w.setBounds(bounds) +} + +// Get window position relative to the screen WorkArea on which it is +func (w *windowsWebviewWindow) relativePosition() (int, int) { + screen, _ := w.getScreen() + pos := screen.absoluteToRelativeDipPoint(w.bounds().Origin()) + // Relative to WorkArea origin + pos.X -= (screen.WorkArea.X - screen.X) + pos.Y -= (screen.WorkArea.Y - screen.Y) + return pos.X, pos.Y +} + +// Set window position relative to the screen WorkArea on which it is +func (w *windowsWebviewWindow) setRelativePosition(x int, y int) { + screen, _ := w.getScreen() + pos := screen.relativeToAbsoluteDipPoint(Point{X: x, Y: y}) + // Relative to WorkArea origin + pos.X += (screen.WorkArea.X - screen.X) + pos.Y += (screen.WorkArea.Y - screen.Y) + w.setPosition(pos.X, pos.Y) +} + +func (w *windowsWebviewWindow) destroy() { + w.parent.markAsDestroyed() + if w.dropTarget != nil { + w.dropTarget.Release() + } + // destroy the window + w32.DestroyWindow(w.hwnd) +} + +func (w *windowsWebviewWindow) reload() { + w.execJS("window.location.reload();") +} + +func (w *windowsWebviewWindow) forceReload() { + // noop +} + +func (w *windowsWebviewWindow) zoomReset() { + w.setZoom(1.0) +} + +func (w *windowsWebviewWindow) zoomIn() { + // Increase the zoom level by 0.05 + currentZoom := w.getZoom() + if currentZoom == -1 { + return + } + w.setZoom(currentZoom + 0.05) +} + +func (w *windowsWebviewWindow) zoomOut() { + // Decrease the zoom level by 0.05 + currentZoom := w.getZoom() + if currentZoom == -1 { + return + } + if currentZoom > 1.05 { + // Decrease the zoom level by 0.05 + w.setZoom(currentZoom - 0.05) + } else { + // Set the zoom level to 1.0 + w.setZoom(1.0) + } +} + +func (w *windowsWebviewWindow) getZoom() float64 { + controller := w.chromium.GetController() + factor, err := controller.GetZoomFactor() + if err != nil { + return -1 + } + return factor +} + +func (w *windowsWebviewWindow) setZoom(zoom float64) { + w.chromium.PutZoomFactor(zoom) +} + +func (w *windowsWebviewWindow) close() { + // Send WM_CLOSE message to trigger the same flow as clicking the X button + w32.SendMessage(w.hwnd, w32.WM_CLOSE, 0, 0) +} + +func (w *windowsWebviewWindow) zoom() { + // Noop +} + +func (w *windowsWebviewWindow) setHTML(html string) { + // Render the given HTML in the webview window + w.execJS(fmt.Sprintf("document.documentElement.innerHTML = %q;", html)) +} + +// on is used to indicate that a particular event should be listened for +func (w *windowsWebviewWindow) on(_ uint) { + // We don't need to worry about this in Windows as we do not need + // to optimise cgo calls +} + +func (w *windowsWebviewWindow) minimise() { + w32.ShowWindow(w.hwnd, w32.SW_MINIMIZE) +} + +func (w *windowsWebviewWindow) unminimise() { + w.restore() +} + +func (w *windowsWebviewWindow) maximise() { + w32.ShowWindow(w.hwnd, w32.SW_MAXIMIZE) + w.chromium.Focus() +} + +func (w *windowsWebviewWindow) unmaximise() { + w.restore() + w.parent.emit(events.Windows.WindowUnMaximise) +} + +func (w *windowsWebviewWindow) restore() { + w32.ShowWindow(w.hwnd, w32.SW_RESTORE) + w.chromium.Focus() +} + +func (w *windowsWebviewWindow) fullscreen() { + if w.isFullscreen() { + return + } + if w.framelessWithDecorations() { + err := w32.ExtendFrameIntoClientArea(w.hwnd, false) + if err != nil { + globalApplication.handleFatalError(err) + } + } + w.disableSizeConstraints() + w.previousWindowStyle = uint32(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) + w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTOPRIMARY) + var monitorInfo w32.MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if !w32.GetMonitorInfo(monitor, &monitorInfo) { + return + } + if !w32.GetWindowPlacement(w.hwnd, &w.previousWindowPlacement) { + return + } + // According to https://devblogs.microsoft.com/oldnewthing/20050505-04/?p=35703 one should use w32.WS_POPUP | w32.WS_VISIBLE + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE)) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME)) + w.isCurrentlyFullscreen = true + w32.SetWindowPos(w.hwnd, w32.HWND_TOP, + int(monitorInfo.RcMonitor.Left), + int(monitorInfo.RcMonitor.Top), + int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left), + int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top), + w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) + + // Hide the menubar in fullscreen mode + w32.SetMenu(w.hwnd, 0) + + w.chromium.Focus() + w.parent.emit(events.Windows.WindowFullscreen) +} + +func (w *windowsWebviewWindow) unfullscreen() { + if !w.isFullscreen() { + return + } + if w.framelessWithDecorations() { + err := w32.ExtendFrameIntoClientArea(w.hwnd, true) + if err != nil { + globalApplication.handleFatalError(err) + } + } + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle) + w32.SetWindowPlacement(w.hwnd, &w.previousWindowPlacement) + w.isCurrentlyFullscreen = false + + // Restore the menubar when exiting fullscreen + if w.menu != nil { + w32.SetMenu(w.hwnd, w.menu.menu) + } + + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, + w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED) + w.enableSizeConstraints() + w.parent.emit(events.Windows.WindowUnFullscreen) +} + +func (w *windowsWebviewWindow) isMinimised() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_MINIMIZE != 0 +} + +func (w *windowsWebviewWindow) isMaximised() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_MAXIMIZE != 0 +} + +func (w *windowsWebviewWindow) isFocused() bool { + // Returns true if the window is currently focused + return w32.GetForegroundWindow() == w.hwnd +} + +func (w *windowsWebviewWindow) isFullscreen() bool { + // TODO: Actually calculate this based on size of window against screen size + // => stffabi: This flag is essential since it indicates that we are in fullscreen mode even before the native properties + // reflect this, e.g. when needing to know if we are in fullscreen during a wndproc message. + // That's also why this flag is set before SetWindowPos in v2 in fullscreen/unfullscreen. + return w.isCurrentlyFullscreen +} + +func (w *windowsWebviewWindow) isNormal() bool { + return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() +} + +func (w *windowsWebviewWindow) isVisible() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_VISIBLE != 0 +} + +func (w *windowsWebviewWindow) focus() { + w32.SetForegroundWindow(w.hwnd) + + if w.isDisabled() { + return + } + if w.isMinimised() { + w.unminimise() + } + + w.focusingChromium = true + w.chromium.Focus() + w.focusingChromium = false +} + +// printStyle takes a windows style and prints it in a human-readable format +// This is for debugging window style issues +func (w *windowsWebviewWindow) printStyle() { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + fmt.Printf("Style: ") + if style&w32.WS_BORDER != 0 { + fmt.Printf("WS_BORDER ") + } + if style&w32.WS_CAPTION != 0 { + fmt.Printf("WS_CAPTION ") + } + if style&w32.WS_CHILD != 0 { + fmt.Printf("WS_CHILD ") + } + if style&w32.WS_CLIPCHILDREN != 0 { + fmt.Printf("WS_CLIPCHILDREN ") + } + if style&w32.WS_CLIPSIBLINGS != 0 { + fmt.Printf("WS_CLIPSIBLINGS ") + } + if style&w32.WS_DISABLED != 0 { + fmt.Printf("WS_DISABLED ") + } + if style&w32.WS_DLGFRAME != 0 { + fmt.Printf("WS_DLGFRAME ") + } + if style&w32.WS_GROUP != 0 { + fmt.Printf("WS_GROUP ") + } + if style&w32.WS_HSCROLL != 0 { + fmt.Printf("WS_HSCROLL ") + } + if style&w32.WS_MAXIMIZE != 0 { + fmt.Printf("WS_MAXIMIZE ") + } + if style&w32.WS_MAXIMIZEBOX != 0 { + fmt.Printf("WS_MAXIMIZEBOX ") + } + if style&w32.WS_MINIMIZE != 0 { + fmt.Printf("WS_MINIMIZE ") + } + if style&w32.WS_MINIMIZEBOX != 0 { + fmt.Printf("WS_MINIMIZEBOX ") + } + if style&w32.WS_OVERLAPPED != 0 { + fmt.Printf("WS_OVERLAPPED ") + } + if style&w32.WS_POPUP != 0 { + fmt.Printf("WS_POPUP ") + } + if style&w32.WS_SYSMENU != 0 { + fmt.Printf("WS_SYSMENU ") + } + if style&w32.WS_TABSTOP != 0 { + fmt.Printf("WS_TABSTOP ") + } + if style&w32.WS_THICKFRAME != 0 { + fmt.Printf("WS_THICKFRAME ") + } + if style&w32.WS_VISIBLE != 0 { + fmt.Printf("WS_VISIBLE ") + } + if style&w32.WS_VSCROLL != 0 { + fmt.Printf("WS_VSCROLL ") + } + fmt.Printf("\n") + + // Do the same for the extended style + extendedStyle := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + fmt.Printf("Extended Style: ") + if extendedStyle&w32.WS_EX_ACCEPTFILES != 0 { + fmt.Printf("WS_EX_ACCEPTFILES ") + } + if extendedStyle&w32.WS_EX_APPWINDOW != 0 { + fmt.Printf("WS_EX_APPWINDOW ") + } + if extendedStyle&w32.WS_EX_CLIENTEDGE != 0 { + fmt.Printf("WS_EX_CLIENTEDGE ") + } + if extendedStyle&w32.WS_EX_COMPOSITED != 0 { + fmt.Printf("WS_EX_COMPOSITED ") + } + if extendedStyle&w32.WS_EX_CONTEXTHELP != 0 { + fmt.Printf("WS_EX_CONTEXTHELP ") + } + if extendedStyle&w32.WS_EX_CONTROLPARENT != 0 { + fmt.Printf("WS_EX_CONTROLPARENT ") + } + if extendedStyle&w32.WS_EX_DLGMODALFRAME != 0 { + fmt.Printf("WS_EX_DLGMODALFRAME ") + } + if extendedStyle&w32.WS_EX_LAYERED != 0 { + fmt.Printf("WS_EX_LAYERED ") + } + if extendedStyle&w32.WS_EX_LAYOUTRTL != 0 { + fmt.Printf("WS_EX_LAYOUTRTL ") + } + if extendedStyle&w32.WS_EX_LEFT != 0 { + fmt.Printf("WS_EX_LEFT ") + } + if extendedStyle&w32.WS_EX_LEFTSCROLLBAR != 0 { + fmt.Printf("WS_EX_LEFTSCROLLBAR ") + } + if extendedStyle&w32.WS_EX_LTRREADING != 0 { + fmt.Printf("WS_EX_LTRREADING ") + } + if extendedStyle&w32.WS_EX_MDICHILD != 0 { + fmt.Printf("WS_EX_MDICHILD ") + } + if extendedStyle&w32.WS_EX_NOACTIVATE != 0 { + fmt.Printf("WS_EX_NOACTIVATE ") + } + if extendedStyle&w32.WS_EX_NOINHERITLAYOUT != 0 { + fmt.Printf("WS_EX_NOINHERITLAYOUT ") + } + if extendedStyle&w32.WS_EX_NOPARENTNOTIFY != 0 { + fmt.Printf("WS_EX_NOPARENTNOTIFY ") + } + if extendedStyle&w32.WS_EX_NOREDIRECTIONBITMAP != 0 { + fmt.Printf("WS_EX_NOREDIRECTIONBITMAP ") + } + if extendedStyle&w32.WS_EX_OVERLAPPEDWINDOW != 0 { + fmt.Printf("WS_EX_OVERLAPPEDWINDOW ") + } + if extendedStyle&w32.WS_EX_PALETTEWINDOW != 0 { + fmt.Printf("WS_EX_PALETTEWINDOW ") + } + if extendedStyle&w32.WS_EX_RIGHT != 0 { + fmt.Printf("WS_EX_RIGHT ") + } + if extendedStyle&w32.WS_EX_RIGHTSCROLLBAR != 0 { + fmt.Printf("WS_EX_RIGHTSCROLLBAR ") + } + if extendedStyle&w32.WS_EX_RTLREADING != 0 { + fmt.Printf("WS_EX_RTLREADING ") + } + if extendedStyle&w32.WS_EX_STATICEDGE != 0 { + fmt.Printf("WS_EX_STATICEDGE ") + } + if extendedStyle&w32.WS_EX_TOOLWINDOW != 0 { + fmt.Printf("WS_EX_TOOLWINDOW ") + } + if extendedStyle&w32.WS_EX_TOPMOST != 0 { + fmt.Printf("WS_EX_TOPMOST ") + } + if extendedStyle&w32.WS_EX_TRANSPARENT != 0 { + fmt.Printf("WS_EX_TRANSPARENT ") + } + if extendedStyle&w32.WS_EX_WINDOWEDGE != 0 { + fmt.Printf("WS_EX_WINDOWEDGE ") + } + fmt.Printf("\n") + +} + +func (w *windowsWebviewWindow) show() { + // Always show the window container immediately (decouple from WebView state) + // This fixes issue #2861 where efficiency mode prevents window visibility + w32.ShowWindow(w.hwnd, w32.SW_SHOW) + w.windowShown = true + w.showRequested = true + + // Show WebView if navigation has completed + if w.webviewNavigationCompleted { + w.chromium.Show() + // Cancel timeout since we can show immediately + if w.visibilityTimeout != nil { + w.visibilityTimeout.Stop() + w.visibilityTimeout = nil + } + } else { + // Start timeout to show WebView if navigation is delayed (fallback for efficiency mode) + if w.visibilityTimeout == nil { + w.visibilityTimeout = time.AfterFunc(3*time.Second, func() { + // Show WebView even if navigation hasn't completed + // This prevents permanent invisibility in efficiency mode + if !w.webviewNavigationCompleted && w.chromium != nil { + w.chromium.Show() + } + w.visibilityTimeout = nil + }) + } + } +} + +func (w *windowsWebviewWindow) hide() { + w32.ShowWindow(w.hwnd, w32.SW_HIDE) + w.windowShown = false + w.showRequested = false + + // Cancel any pending visibility timeout + if w.visibilityTimeout != nil { + w.visibilityTimeout.Stop() + w.visibilityTimeout = nil + } +} + +// Get the screen for the current window +func (w *windowsWebviewWindow) getScreen() (*Screen, error) { + return getScreenForWindow(w) +} + +func (w *windowsWebviewWindow) setFrameless(b bool) { + // Remove or add the frame + if b { + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_POPUP) + } else { + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_OVERLAPPEDWINDOW) + } + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_FRAMECHANGED) +} + +func newWindowImpl(parent *WebviewWindow) *windowsWebviewWindow { + result := &windowsWebviewWindow{ + parent: parent, + resizeBorderWidth: int32(w32.GetSystemMetrics(w32.SM_CXSIZEFRAME)), + resizeBorderHeight: int32(w32.GetSystemMetrics(w32.SM_CYSIZEFRAME)), + // Initialize visibility tracking fields + showRequested: false, + visibilityTimeout: nil, + windowShown: false, + } + + return result +} + +func (w *windowsWebviewWindow) openContextMenu(menu *Menu, _ *ContextMenuData) { + // Create the menu + thisMenu := NewPopupMenu(w.hwnd, menu) + thisMenu.Update() + w.currentlyOpenContextMenu = thisMenu + thisMenu.ShowAtCursor() +} + +func (w *windowsWebviewWindow) setStyle(b bool, style int) { + currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) + if currentStyle != 0 { + currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + w32.SetWindowLongPtr(w.hwnd, w32.GWL_STYLE, uintptr(currentStyle)) + } +} +func (w *windowsWebviewWindow) setExStyle(b bool, style int) { + currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE)) + if currentStyle != 0 { + currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style) + w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle)) + } +} + +func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) { + if !w32.SupportsBackdropTypes() { + var accent = w32.ACCENT_POLICY{ + AccentState: w32.ACCENT_ENABLE_BLURBEHIND, + } + var data w32.WINDOWCOMPOSITIONATTRIBDATA + data.Attrib = w32.WCA_ACCENT_POLICY + data.PvData = w32.PVOID(&accent) + data.CbData = unsafe.Sizeof(accent) + + w32.SetWindowCompositionAttribute(w.hwnd, &data) + } else { + w32.EnableTranslucency(w.hwnd, uint32(backdropType)) + } +} + +func (w *windowsWebviewWindow) setIcon(icon w32.HICON) { + w32.SendMessage(w.hwnd, w32.WM_SETICON, w32.ICON_BIG, icon) +} + +func (w *windowsWebviewWindow) disableIcon() { + + // TODO: If frameless, return + exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME)) + w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, + uint( + w32.SWP_FRAMECHANGED| + w32.SWP_NOMOVE| + w32.SWP_NOSIZE| + w32.SWP_NOZORDER), + ) +} + +func (w *windowsWebviewWindow) processThemeColour(fn func(w32.HWND, uint32), value *uint32) { + if value == nil { + return + } + fn(w.hwnd, *value) +} + +func (w *windowsWebviewWindow) isDisabled() bool { + style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) + return style&w32.WS_DISABLED != 0 +} + +func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) { + + if w32.IsCurrentlyHighContrastMode() { + return + } + + if !w32.SupportsThemes() { + return + } + + w32.SetTheme(w.hwnd, isDarkMode) + + // Clear any existing theme first + if w.menubarTheme != nil && !isDarkMode { + // Reset menu to default Windows theme when switching to light mode + w.menubarTheme = nil + if w.menu != nil { + // Clear the menu background by setting it to default + var mi w32.MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = w32.MIIM_BACKGROUND | w32.MIIM_APPLYTOSUBMENUS + mi.HbrBack = 0 // NULL brush resets to default + w32.SetMenuInfo(w.menu.menu, &mi) + } + } + + // Custom theme processing + customTheme := w.parent.options.Windows.CustomTheme + // Custom theme + if w32.SupportsCustomThemes() { + var userTheme *MenuBarTheme + if isDarkMode { + userTheme = customTheme.DarkModeMenuBar + } else { + userTheme = customTheme.LightModeMenuBar + } + + if userTheme != nil { + modeStr := "light" + if isDarkMode { + modeStr = "dark" + } + globalApplication.debug("Setting custom "+modeStr+" menubar theme", "window", w.parent.id) + w.menubarTheme = &w32.MenuBarTheme{ + TitleBarBackground: userTheme.Default.Background, + TitleBarText: userTheme.Default.Text, + MenuBarBackground: userTheme.Default.Background, // Use default background for menubar + MenuHoverBackground: userTheme.Hover.Background, + MenuHoverText: userTheme.Hover.Text, + MenuSelectedBackground: userTheme.Selected.Background, + MenuSelectedText: userTheme.Selected.Text, + } + w.menubarTheme.Init() + + // If menu is already set, update it + if w.menu != nil { + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + } else if userTheme == nil && isDarkMode { + // Use default dark theme if no custom theme provided + globalApplication.debug("Setting default dark menubar theme", "window", w.parent.id) + w.menubarTheme = &w32.MenuBarTheme{ + TitleBarBackground: w32.RGBptr(45, 45, 45), // Dark titlebar + TitleBarText: w32.RGBptr(222, 222, 222), // Slightly muted white + MenuBarBackground: w32.RGBptr(33, 33, 33), // Standard dark mode (#212121) + MenuHoverBackground: w32.RGBptr(48, 48, 48), // Slightly lighter for hover (#303030) + MenuHoverText: w32.RGBptr(222, 222, 222), // Slightly muted white + MenuSelectedBackground: w32.RGBptr(48, 48, 48), // Same as hover + MenuSelectedText: w32.RGBptr(222, 222, 222), // Slightly muted white + } + w.menubarTheme.Init() + + // If menu is already set, update it + if w.menu != nil { + w.menubarTheme.SetMenuBackground(w.menu.menu) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + } else if userTheme == nil && !isDarkMode && w.menu != nil { + // No custom theme for light mode - ensure menu is reset to default + globalApplication.debug("Resetting menu to default light theme", "window", w.parent.id) + var mi w32.MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = w32.MIIM_BACKGROUND | w32.MIIM_APPLYTOSUBMENUS + mi.HbrBack = 0 // NULL brush resets to default + w32.SetMenuInfo(w.menu.menu, &mi) + w32.DrawMenuBar(w.hwnd) + w32.InvalidateRect(w.hwnd, nil, true) + } + // Define a map for theme selection + themeMap := map[bool]map[bool]*WindowTheme{ + true: { // Window is active + true: customTheme.DarkModeActive, // Dark mode + false: customTheme.LightModeActive, // Light mode + }, + false: { // Window is inactive + true: customTheme.DarkModeInactive, // Dark mode + false: customTheme.LightModeInactive, // Light mode + }, + } + + // Select the appropriate theme + theme := themeMap[w.isActive()][isDarkMode] + + // Apply theme colors + if theme != nil { + w.processThemeColour(w32.SetTitleBarColour, theme.TitleBarColour) + w.processThemeColour(w32.SetTitleTextColour, theme.TitleTextColour) + w.processThemeColour(w32.SetBorderColour, theme.BorderColour) + } + } +} + +func (w *windowsWebviewWindow) isActive() bool { + return w32.GetForegroundWindow() == w.hwnd +} + +var resizePending int32 + +func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr { + + // Use the original implementation that works perfectly for maximized + processed, code := w32.MenuBarWndProc(w.hwnd, msg, wparam, lparam, w.menubarTheme) + if processed { + return code + } + + switch msg { + case w32.WM_ACTIVATE: + if int(wparam&0xffff) == w32.WA_INACTIVE { + w.parent.emit(events.Windows.WindowInactive) + } + if wparam == w32.WA_ACTIVE { + getNativeApplication().currentWindowID = w.parent.id + w.parent.emit(events.Windows.WindowActive) + } + if wparam == w32.WA_CLICKACTIVE { + getNativeApplication().currentWindowID = w.parent.id + w.parent.emit(events.Windows.WindowClickActive) + } + // If we want to have a frameless window but with the default frame decorations, extend the DWM client area. + // This Option is not affected by returning 0 in WM_NCCALCSIZE. + // As a result we have hidden the titlebar but still have the default window frame styling. + // See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks + if w.framelessWithDecorations() { + err := w32.ExtendFrameIntoClientArea(w.hwnd, true) + if err != nil { + globalApplication.handleFatalError(err) + } + } + case w32.WM_CLOSE: + + if atomic.LoadUint32(&w.parent.unconditionallyClose) == 0 { + // We were called by `Close()` or pressing the close button on the window + w.parent.emit(events.Windows.WindowClosing) + return 0 + } + + defer func() { + windowsApp := globalApplication.impl.(*windowsApp) + windowsApp.unregisterWindow(w) + + }() + + // Now do the actual close + w.chromium.ShuttingDown() + return w32.DefWindowProc(w.hwnd, w32.WM_CLOSE, 0, 0) + + case w32.WM_KILLFOCUS: + if w.focusingChromium { + return 0 + } + w.parent.emit(events.Windows.WindowKillFocus) + case w32.WM_ENTERSIZEMOVE: + // This is needed to close open dropdowns when moving the window https://github.com/MicrosoftEdge/WebView2Feedback/issues/2290 + w32.SetFocus(w.hwnd) + if int(w32.GetKeyState(w32.VK_LBUTTON))&(0x8000) != 0 { + // Left mouse button is down - window is being moved + w.parent.emit(events.Windows.WindowStartMove) + } else { + // Window is being resized + w.parent.emit(events.Windows.WindowStartResize) + } + case w32.WM_EXITSIZEMOVE: + if int(w32.GetKeyState(w32.VK_LBUTTON))&0x8000 != 0 { + w.parent.emit(events.Windows.WindowEndMove) + } else { + w.parent.emit(events.Windows.WindowEndResize) + } + case w32.WM_SETFOCUS: + w.focus() + w.parent.emit(events.Windows.WindowSetFocus) + case w32.WM_MOVE, w32.WM_MOVING: + _ = w.chromium.NotifyParentWindowPositionChanged() + w.moveDebouncer(func() { + w.parent.emit(events.Windows.WindowDidMove) + }) + case w32.WM_SHOWWINDOW: + if wparam == 1 { + w.parent.emit(events.Windows.WindowShow) + } else { + w.parent.emit(events.Windows.WindowHide) + } + case w32.WM_WINDOWPOSCHANGED: + windowPos := (*w32.WINDOWPOS)(unsafe.Pointer(lparam)) + if windowPos.Flags&w32.SWP_NOZORDER == 0 { + w.parent.emit(events.Windows.WindowZOrderChanged) + } + case w32.WM_PAINT: + w.parent.emit(events.Windows.WindowPaint) + case w32.WM_ERASEBKGND: + w.parent.emit(events.Windows.WindowBackgroundErase) + return 1 // Let WebView2 handle background erasing + // WM_UAHDRAWMENUITEM is handled by MenuBarWndProc at the top of this function + // Check for keypress + case w32.WM_SYSCOMMAND: + switch wparam { + case w32.SC_KEYMENU: + if lparam == 0 { + // F10 or plain Alt key + if w.processKeyBinding(w32.VK_F10) { + return 0 + } + } else { + // Alt + key combination + // The character code is in the low word of lparam + char := byte(lparam & 0xFF) + // Convert ASCII to virtual key code if needed + vkey := w32.VkKeyScan(uint16(char)) + if w.processKeyBinding(uint(vkey)) { + return 0 + } + } + } + case w32.WM_SYSKEYDOWN: + globalApplication.info("w32.WM_SYSKEYDOWN: %v", uint(wparam)) + w.parent.emit(events.Windows.WindowKeyDown) + if w.processKeyBinding(uint(wparam)) { + return 0 + } + case w32.WM_SYSKEYUP: + w.parent.emit(events.Windows.WindowKeyUp) + case w32.WM_KEYDOWN: + w.parent.emit(events.Windows.WindowKeyDown) + w.processKeyBinding(uint(wparam)) + case w32.WM_KEYUP: + w.parent.emit(events.Windows.WindowKeyUp) + case w32.WM_SIZE: + switch wparam { + case w32.SIZE_MAXIMIZED: + w.parent.emit(events.Windows.WindowMaximise) + // Force complete redraw when maximized + if w.menu != nil && w.menubarTheme != nil { + // Invalidate the entire window to force complete redraw + w32.RedrawWindow(w.hwnd, nil, 0, w32.RDW_FRAME|w32.RDW_INVALIDATE|w32.RDW_UPDATENOW) + } + case w32.SIZE_RESTORED: + if w.isMinimizing { + w.parent.emit(events.Windows.WindowUnMinimise) + } + w.isMinimizing = false + w.parent.emit(events.Windows.WindowRestore) + case w32.SIZE_MINIMIZED: + w.isMinimizing = true + w.parent.emit(events.Windows.WindowMinimise) + } + + doResize := func() { + // Get the new size from lparam + width := int32(lparam & 0xFFFF) + height := int32((lparam >> 16) & 0xFFFF) + bounds := &edge.Rect{ + Left: 0, + Top: 0, + Right: width, + Bottom: height, + } + InvokeSync(func() { + time.Sleep(1 * time.Nanosecond) + w.chromium.ResizeWithBounds(bounds) + atomic.StoreInt32(&resizePending, 0) + w.parent.emit(events.Windows.WindowDidResize) + }) + } + + if w.parent.options.Frameless && wparam == w32.SIZE_MINIMIZED { + // If the window is frameless, and we are minimizing, then we need to suppress the Resize on the + // WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong + // size during the restore animation and only fully renders when the animation is done. This highly + // depends on the content in the WebView, see https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549 + } else if w.resizeDebouncer != nil { + w.resizeDebouncer(doResize) + } else { + if atomic.CompareAndSwapInt32(&resizePending, 0, 1) { + doResize() + } + } + return 0 + + case w32.WM_GETMINMAXINFO: + mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam)) + hasConstraints := false + options := w.parent.options + // Using ScreenManager to get the closest screen and scale according to its DPI is problematic + // here because in multi-monitor setup, when dragging the window between monitors with the mouse + // on the side with the higher DPI, the DPI change point is offset beyond the mid point, causing + // wrong scaling and unwanted resizing when using the monitor DPI. To avoid this issue, we use + // scaleWithWindowDPI() instead which retrieves the correct DPI with GetDpiForWindow(). + if options.MinWidth > 0 || options.MinHeight > 0 { + hasConstraints = true + + width, height := w.scaleWithWindowDPI(options.MinWidth, options.MinHeight) + if width > 0 { + mmi.PtMinTrackSize.X = int32(width) + } + if height > 0 { + mmi.PtMinTrackSize.Y = int32(height) + } + } + if options.MaxWidth > 0 || options.MaxHeight > 0 { + hasConstraints = true + + width, height := w.scaleWithWindowDPI(options.MaxWidth, options.MaxHeight) + if width > 0 { + mmi.PtMaxTrackSize.X = int32(width) + } + if height > 0 { + mmi.PtMaxTrackSize.Y = int32(height) + } + } + if hasConstraints { + return 0 + } + + case w32.WM_DPICHANGED: + if !w.ignoreDPIChangeResizing { + newWindowRect := (*w32.RECT)(unsafe.Pointer(lparam)) + w32.SetWindowPos(w.hwnd, + uintptr(0), + int(newWindowRect.Left), + int(newWindowRect.Top), + int(newWindowRect.Right-newWindowRect.Left), + int(newWindowRect.Bottom-newWindowRect.Top), + w32.SWP_NOZORDER|w32.SWP_NOACTIVATE) + } + w.parent.emit(events.Windows.WindowDPIChanged) + } + + if w.parent.options.Windows.WindowMask != nil { + switch msg { + case w32.WM_NCHITTEST: + if w.parent.options.Windows.WindowMaskDraggable { + return w32.HTCAPTION + } + w.parent.emit(events.Windows.WindowNonClientHit) + return w32.HTCLIENT + case w32.WM_NCLBUTTONDOWN: + w.parent.emit(events.Windows.WindowNonClientMouseDown) + case w32.WM_NCLBUTTONUP: + w.parent.emit(events.Windows.WindowNonClientMouseUp) + case w32.WM_NCMOUSEMOVE: + w.parent.emit(events.Windows.WindowNonClientMouseMove) + case w32.WM_NCMOUSELEAVE: + w.parent.emit(events.Windows.WindowNonClientMouseLeave) + } + } + + if w.menu != nil || w.currentlyOpenContextMenu != nil { + switch msg { + case w32.WM_COMMAND: + cmdMsgID := int(wparam & 0xffff) + switch cmdMsgID { + default: + var processed bool + if w.currentlyOpenContextMenu != nil { + processed = w.currentlyOpenContextMenu.ProcessCommand(cmdMsgID) + w.currentlyOpenContextMenu = nil + + } + if !processed && w.menu != nil { + processed = w.menu.ProcessCommand(cmdMsgID) + } + } + } + } + + if options := w.parent.options; options.Frameless { + switch msg { + case w32.WM_ACTIVATE: + // If we want to have a frameless window but with the default frame decorations, extend the DWM client area. + // This Option is not affected by returning 0 in WM_NCCALCSIZE. + // As a result we have hidden the titlebar but still have the default window frame styling. + // See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks + if w.framelessWithDecorations() { + err := w32.ExtendFrameIntoClientArea(w.hwnd, true) + if err != nil { + globalApplication.handleFatalError(err) + } + } + + case w32.WM_NCCALCSIZE: + // Disable the standard frame by allowing the client area to take the full + // window size. + // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks + // This hides the titlebar and also disables the resizing from user interaction because the standard frame is not + // shown. We still need the WS_THICKFRAME style to enable resizing from the frontend. + if wparam != 0 { + rgrc := (*w32.RECT)(unsafe.Pointer(lparam)) + if w.isCurrentlyFullscreen { + // In Full-Screen mode we don't need to adjust anything + // It essential we have the flag here, that is set before SetWindowPos in fullscreen/unfullscreen + // because the native size might not yet reflect we are in fullscreen during this event! + w.setPadding(edge.Rect{}) + } else if w.isMaximised() { + // If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise + // some content goes beyond the visible part of the monitor. + // Make sure to use the provided RECT to get the monitor, because during maximizig there might be + // a wrong monitor returned in multiscreen mode when using MonitorFromWindow. + // See: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549 + screen := ScreenNearestPhysicalRect(Rect{ + X: int(rgrc.Left), + Y: int(rgrc.Top), + Width: int(rgrc.Right - rgrc.Left), + Height: int(rgrc.Bottom - rgrc.Top), + }) + + rect := screen.PhysicalWorkArea + + maxWidth := options.MaxWidth + maxHeight := options.MaxHeight + + if maxWidth > 0 { + maxWidth = screen.scale(maxWidth, false) + if rect.Width > maxWidth { + rect.Width = maxWidth + } + } + + if maxHeight > 0 { + maxHeight = screen.scale(maxHeight, false) + if rect.Height > maxHeight { + rect.Height = maxHeight + } + } + + *rgrc = w32.RECT{ + Left: int32(rect.X), + Top: int32(rect.Y), + Right: int32(rect.X + rect.Width), + Bottom: int32(rect.Y + rect.Height), + } + w.setPadding(edge.Rect{}) + } else { + // This is needed to work around the resize flickering in frameless mode with WindowDecorations + // See: https://stackoverflow.com/a/6558508 + // The workaround from the SO answer suggests to reduce the bottom of the window by 1px. + // However, this would result in losing 1px of the WebView content. + // Increasing the bottom also worksaround the flickering, but we would lose 1px of the WebView content + // therefore let's pad the content with 1px at the bottom. + rgrc.Bottom += 1 + w.setPadding(edge.Rect{Bottom: 1}) + } + return 0 + } + } + } + return w32.DefWindowProc(w.hwnd, msg, wparam, lparam) +} + +func (w *windowsWebviewWindow) DPI() (w32.UINT, w32.UINT) { + if w32.HasGetDpiForWindowFunc() { + // GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accurate + // one, especially it is consistent with the WM_DPICHANGED event. + dpi := w32.GetDpiForWindow(w.hwnd) + return dpi, dpi + } + + if w32.HasGetDPIForMonitorFunc() { + // GetDpiForWindow is supported beginning with Windows 8.1 + monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST) + if monitor == 0 { + return 0, 0 + } + var dpiX, dpiY w32.UINT + w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) + return dpiX, dpiY + } + + // If none of the above is supported fallback to the System DPI. + screen := w32.GetDC(0) + x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX) + y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY) + w32.ReleaseDC(0, screen) + return w32.UINT(x), w32.UINT(y) +} + +func (w *windowsWebviewWindow) scaleWithWindowDPI(width, height int) (int, int) { + dpix, dpiy := w.DPI() + scaledWidth := ScaleWithDPI(width, dpix) + scaledHeight := ScaleWithDPI(height, dpiy) + + return scaledWidth, scaledHeight +} + +func ScaleWithDPI(pixels int, dpi uint) int { + return (pixels * int(dpi)) / 96 +} + +func (w *windowsWebviewWindow) setWindowMask(imageData []byte) { + + // Set the window to a WS_EX_LAYERED window + newStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) | w32.WS_EX_LAYERED + + if w.isAlwaysOnTop() { + newStyle |= w32.WS_EX_TOPMOST + } + // Save the current window style + w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)) + + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(newStyle)) + + data, err := pngToImage(imageData) + if err != nil { + globalApplication.fatal("fatal error in callback setWindowMask: %w", err) + } + + bitmap, err := w32.CreateHBITMAPFromImage(data) + hdc := w32.CreateCompatibleDC(0) + defer w32.DeleteDC(hdc) + + oldBitmap := w32.SelectObject(hdc, bitmap) + defer w32.SelectObject(hdc, oldBitmap) + + screenDC := w32.GetDC(0) + defer w32.ReleaseDC(0, screenDC) + + size := w32.SIZE{CX: int32(data.Bounds().Dx()), CY: int32(data.Bounds().Dy())} + ptSrc := w32.POINT{X: 0, Y: 0} + ptDst := w32.POINT{X: int32(w.width()), Y: int32(w.height())} + blend := w32.BLENDFUNCTION{ + BlendOp: w32.AC_SRC_OVER, + BlendFlags: 0, + SourceConstantAlpha: 255, + AlphaFormat: w32.AC_SRC_ALPHA, + } + w32.UpdateLayeredWindow(w.hwnd, screenDC, &ptDst, &size, hdc, &ptSrc, 0, &blend, w32.ULW_ALPHA) +} + +func (w *windowsWebviewWindow) isAlwaysOnTop() bool { + return w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)&w32.WS_EX_TOPMOST != 0 +} + +// processMessage is given a message sent from JS via the postMessage API +// We put it on the global window message buffer to be processed centrally +func (w *windowsWebviewWindow) processMessage(message string) { + // We send all messages to the centralised window message buffer + windowMessageBuffer <- &windowMessage{ + windowId: w.parent.id, + message: message, + } +} + +func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { + + // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but + // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. + if reqHeaders, err := req.GetHeaders(); err == nil { + useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) + useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") + err = reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) + if err != nil { + globalApplication.fatal("error setting UserAgent header: %w", err) + } + err = reqHeaders.SetHeader(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(w.parent.id), 10)) + if err != nil { + globalApplication.fatal("error setting WindowId header: %w", err) + } + err = reqHeaders.Release() + if err != nil { + globalApplication.fatal("error releasing headers: %w", err) + } + } + + if globalApplication.assets == nil { + // We are using the devServer let the WebView2 handle the request with its default handler + return + } + + //Get the request + uri, _ := req.GetUri() + reqUri, err := url.ParseRequestURI(uri) + if err != nil { + globalApplication.error("unable to parse request uri: uri='%s' error='%w'", uri, err) + return + } + + if reqUri.Scheme != "http" { + // Let the WebView2 handle the request with its default handler + return + } else if !strings.HasPrefix(reqUri.Host, "wails.localhost") { + // Let the WebView2 handle the request with its default handler + return + } + + webviewRequest, err := webview.NewRequest( + w.chromium.Environment(), + args, + func(fn func()) { + InvokeSync(fn) + }) + if err != nil { + globalApplication.error("%s: NewRequest failed: %w", uri, err) + return + } + + webviewRequests <- &webViewAssetRequest{ + Request: webviewRequest, + windowId: w.parent.id, + windowName: w.parent.options.Name, + } +} + +func (w *windowsWebviewWindow) setupChromium() { + chromium := w.chromium + debugMode := globalApplication.isDebugMode + + opts := w.parent.options.Windows + + webview2version, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(globalApplication.options.Windows.WebviewBrowserPath) + if err != nil { + globalApplication.error("error getting WebView2 version: %w", err) + return + } + globalApplication.capabilities = capabilities.NewCapabilities(webview2version) + + // We disable this by default. Can be overridden with the `EnableFraudulentWebsiteWarnings` option + opts.DisabledFeatures = append(opts.DisabledFeatures, "msSmartScreenProtection") + + if len(opts.DisabledFeatures) > 0 { + opts.DisabledFeatures = lo.Uniq(opts.DisabledFeatures) + arg := fmt.Sprintf("--disable-features=%s", strings.Join(opts.DisabledFeatures, ",")) + chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) + } + + if len(opts.EnabledFeatures) > 0 { + opts.EnabledFeatures = lo.Uniq(opts.EnabledFeatures) + arg := fmt.Sprintf("--enable-features=%s", strings.Join(opts.EnabledFeatures, ",")) + chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) + } + + chromium.DataPath = globalApplication.options.Windows.WebviewUserDataPath + chromium.BrowserPath = globalApplication.options.Windows.WebviewBrowserPath + + if opts.Permissions != nil { + for permission, state := range opts.Permissions { + chromium.SetPermission(edge.CoreWebView2PermissionKind(permission), + edge.CoreWebView2PermissionState(state)) + } + } + + chromium.MessageCallback = w.processMessage + chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects + chromium.WebResourceRequestedCallback = w.processRequest + chromium.ContainsFullScreenElementChangedCallback = w.fullscreenChanged + chromium.NavigationCompletedCallback = w.navigationCompleted + chromium.AcceleratorKeyCallback = w.processKeyBinding + + chromium.Embed(w.hwnd) + + // Prevent efficiency mode by keeping WebView2 visible (fixes issue #2861) + // Microsoft recommendation: keep IsVisible = true to avoid efficiency mode + // See: https://github.com/MicrosoftEdge/WebView2Feedback/discussions/4021 + // TODO: Re-enable when PutIsVisible method is available in go-webview2 package + // err := chromium.PutIsVisible(true) + // if err != nil { + // globalApplication.error("Failed to set WebView2 visibility for efficiency mode prevention: %v", err) + // } + + if chromium.HasCapability(edge.SwipeNavigation) { + err := chromium.PutIsSwipeNavigationEnabled(opts.EnableSwipeGestures) + if err != nil { + globalApplication.handleFatalError(err) + } + } + + if chromium.HasCapability(edge.AllowExternalDrop) { + err := chromium.AllowExternalDrag(false) + if err != nil { + globalApplication.handleFatalError(err) + } + } + if w.parent.options.EnableDragAndDrop { + w.dropTarget = w32.NewDropTarget() + w.dropTarget.OnDrop = func(files []string) { + w.parent.emit(events.Windows.WindowDragDrop) + windowDragAndDropBuffer <- &dragAndDropMessage{ + windowId: windowID, + filenames: files, + } + } + if opts.OnEnterEffect != 0 { + w.dropTarget.OnEnterEffect = convertEffect(opts.OnEnterEffect) + } + if opts.OnOverEffect != 0 { + w.dropTarget.OnOverEffect = convertEffect(opts.OnOverEffect) + } + w.dropTarget.OnEnter = func() { + w.parent.emit(events.Windows.WindowDragEnter) + } + w.dropTarget.OnLeave = func() { + w.parent.emit(events.Windows.WindowDragLeave) + } + w.dropTarget.OnOver = func() { + w.parent.emit(events.Windows.WindowDragOver) + } + // Enumerate all the child windows for this window and register them as drop targets + w32.EnumChildWindows(w.hwnd, func(hwnd w32.HWND, lparam w32.LPARAM) w32.LRESULT { + // Check if the window class is "Chrome_RenderWidgetHostHWND" + // If it is, then we register it as a drop target + //windowName := w32.GetClassName(hwnd) + //println(windowName) + //if windowName == "Chrome_RenderWidgetHostHWND" { + err := w32.RegisterDragDrop(hwnd, w.dropTarget) + if err != nil && !errors.Is(err, syscall.Errno(w32.DRAGDROP_E_ALREADYREGISTERED)) { + globalApplication.error("error registering drag and drop: %w", err) + } + //} + return 1 + }) + + } + + err = chromium.PutIsGeneralAutofillEnabled(opts.GeneralAutofillEnabled) + if err != nil { + if errors.Is(err, edge.UnsupportedCapabilityError) { + globalApplication.warning("unsupported capability: GeneralAutofillEnabled") + } else { + globalApplication.handleFatalError(err) + } + } + + err = chromium.PutIsPasswordAutosaveEnabled(opts.PasswordAutosaveEnabled) + if err != nil { + if errors.Is(err, edge.UnsupportedCapabilityError) { + globalApplication.warning("unsupported capability: PasswordAutosaveEnabled") + } else { + globalApplication.handleFatalError(err) + } + } + + // We will get round to this + //if chromium.HasCapability(edge.AllowExternalDrop) { + // err := chromium.AllowExternalDrag(w.parent.options.EnableDragAndDrop) + // if err != nil { + // globalApplication.handleFatalError(err) + // } + // if w.parent.options.EnableDragAndDrop { + // chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects + // } + //} + + chromium.Resize() + settings, err := chromium.GetSettings() + if err != nil { + globalApplication.handleFatalError(err) + } + if settings == nil { + globalApplication.fatal("error getting settings") + } + err = settings.PutAreDefaultContextMenusEnabled(debugMode || !w.parent.options.DefaultContextMenuDisabled) + if err != nil { + globalApplication.handleFatalError(err) + } + + w.enableDevTools(settings) + + if w.parent.options.Zoom > 0.0 { + chromium.PutZoomFactor(w.parent.options.Zoom) + } + err = settings.PutIsZoomControlEnabled(w.parent.options.ZoomControlEnabled) + if err != nil { + globalApplication.handleFatalError(err) + } + + err = settings.PutIsStatusBarEnabled(false) + if err != nil { + globalApplication.handleFatalError(err) + } + err = settings.PutAreBrowserAcceleratorKeysEnabled(false) + if err != nil { + globalApplication.handleFatalError(err) + } + err = settings.PutIsSwipeNavigationEnabled(false) + if err != nil { + globalApplication.handleFatalError(err) + } + + if debugMode && w.parent.options.OpenInspectorOnStartup { + chromium.OpenDevToolsWindow() + } + + // Set background colour + w.setBackgroundColour(w.parent.options.BackgroundColour) + chromium.SetBackgroundColour(w.parent.options.BackgroundColour.Red, w.parent.options.BackgroundColour.Green, w.parent.options.BackgroundColour.Blue, w.parent.options.BackgroundColour.Alpha) + + chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) + chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL) + + if w.parent.options.HTML != "" { + var script string + if w.parent.options.JS != "" { + script = w.parent.options.JS + } + if w.parent.options.CSS != "" { + script += fmt.Sprintf("; addEventListener(\"DOMContentLoaded\", (event) => { document.head.appendChild(document.createElement('style')).innerHTML=\"%s\"; });", strings.ReplaceAll(w.parent.options.CSS, `"`, `\"`)) + } + if script != "" { + chromium.Init(script) + } + chromium.NavigateToString(w.parent.options.HTML) + } else { + startURL, err := assetserver.GetStartURL(w.parent.options.URL) + if err != nil { + globalApplication.handleFatalError(err) + } + w.webviewNavigationCompleted = false + chromium.Navigate(startURL) + } + +} + +func (w *windowsWebviewWindow) fullscreenChanged(sender *edge.ICoreWebView2, _ *edge.ICoreWebView2ContainsFullScreenElementChangedEventArgs) { + isFullscreen, err := sender.GetContainsFullScreenElement() + if err != nil { + globalApplication.fatal("fatal error in callback fullscreenChanged: %w", err) + } + if isFullscreen { + w.fullscreen() + } else { + w.unfullscreen() + } +} + +func convertEffect(effect DragEffect) w32.DWORD { + switch effect { + case DragEffectCopy: + return w32.DROPEFFECT_COPY + case DragEffectMove: + return w32.DROPEFFECT_MOVE + case DragEffectLink: + return w32.DROPEFFECT_LINK + default: + return w32.DROPEFFECT_NONE + } +} + +func (w *windowsWebviewWindow) flash(enabled bool) { + w32.FlashWindow(w.hwnd, enabled) +} + +func (w *windowsWebviewWindow) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) { + + // Install the runtime core + w.execJS(runtime.Core()) + + // EmitEvent DomReady ApplicationEvent + windowEvents <- &windowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id} + + if w.webviewNavigationCompleted { + // NavigationCompleted is triggered for every Load. If an application uses reloads the Hide/Show will trigger + // a flickering of the window with every reload. So we only do this once for the first NavigationCompleted. + return + } + w.webviewNavigationCompleted = true + + // Cancel any pending visibility timeout since navigation completed + if w.visibilityTimeout != nil { + w.visibilityTimeout.Stop() + w.visibilityTimeout = nil + } + + wasFocused := w.isFocused() + // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026 + err := w.chromium.Hide() + if err != nil { + globalApplication.handleFatalError(err) + } + err = w.chromium.Show() + if err != nil { + globalApplication.handleFatalError(err) + } + if wasFocused { + w.focus() + } + + // Only call parent.Show() if not hidden and show was requested but window wasn't shown yet + // The new robust show() method handles window visibility independently + if !w.parent.options.Hidden { + if w.showRequested && !w.windowShown { + w.parent.Show() + } + w.update() + } +} + +func (w *windowsWebviewWindow) processKeyBinding(vkey uint) bool { + + globalApplication.debug("Processing key binding", "vkey", vkey) + + // Get the keyboard state and convert to an accelerator + var keyState [256]byte + if !w32.GetKeyboardState(keyState[:]) { + globalApplication.error("error getting keyboard state") + return false + } + + var acc accelerator + // Check if CTRL is pressed + if keyState[w32.VK_CONTROL]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, ControlKey) + } + // Check if ALT is pressed + if keyState[w32.VK_MENU]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, OptionOrAltKey) + } + // Check if SHIFT is pressed + if keyState[w32.VK_SHIFT]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, ShiftKey) + } + // Check if WIN is pressed + if keyState[w32.VK_LWIN]&0x80 != 0 || keyState[w32.VK_RWIN]&0x80 != 0 { + acc.Modifiers = append(acc.Modifiers, SuperKey) + } + + if vkey != w32.VK_CONTROL && vkey != w32.VK_MENU && vkey != w32.VK_SHIFT && vkey != w32.VK_LWIN && vkey != w32.VK_RWIN { + // Convert the vkey to a string + accKey, ok := VirtualKeyCodes[vkey] + if !ok { + return false + } + acc.Key = accKey + } + + accKey := acc.String() + globalApplication.debug("Processing key binding", "vkey", vkey, "acc", accKey) + + // Process the key binding + if w.parent.processKeyBinding(accKey) { + return true + } + + if accKey == "alt+f4" { + w32.PostMessage(w.hwnd, w32.WM_CLOSE, 0, 0) + return true + } + + return false +} + +func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) { + if strings.HasPrefix(message, "FilesDropped") { + objs, err := args.GetAdditionalObjects() + if err != nil { + globalApplication.handleError(err) + return + } + + defer func() { + err = objs.Release() + if err != nil { + globalApplication.error("error releasing objects: %w", err) + } + }() + + count, err := objs.GetCount() + if err != nil { + globalApplication.error("cannot get count: %w", err) + return + } + + var filenames []string + for i := uint32(0); i < count; i++ { + _file, err := objs.GetValueAtIndex(i) + if err != nil { + globalApplication.error("cannot get value at %d: %w", i, err) + return + } + + file := (*edge.ICoreWebView2File)(unsafe.Pointer(_file)) + + // TODO: Fix this + defer file.Release() + + filepath, err := file.GetPath() + if err != nil { + globalApplication.error("cannot get path for object at %d: %w", i, err) + return + } + + filenames = append(filenames, filepath) + } + + addDragAndDropMessage(w.parent.id, filenames) + return + } +} + +func (w *windowsWebviewWindow) setMaximiseButtonEnabled(enabled bool) { + w.setStyle(enabled, w32.WS_MAXIMIZEBOX) +} + +func (w *windowsWebviewWindow) setMinimiseButtonEnabled(enabled bool) { + w.setStyle(enabled, w32.WS_MINIMIZEBOX) +} + +func (w *windowsWebviewWindow) toggleMenuBar() { + if w.menu != nil { + if w32.GetMenu(w.hwnd) == 0 { + w32.SetMenu(w.hwnd, w.menu.menu) + } else { + w32.SetMenu(w.hwnd, 0) + } + + // Get the bounds of the client area + //bounds := w32.GetClientRect(w.hwnd) + + // Resize the webview + w.chromium.Resize() + + // Update size of webview + w.update() + // Restore focus to the webview after toggling menu + w.focus() + } +} + +func (w *windowsWebviewWindow) enableRedraw() { + w32.SendMessage(w.hwnd, w32.WM_SETREDRAW, 1, 0) + w32.RedrawWindow(w.hwnd, nil, 0, w32.RDW_ERASE|w32.RDW_FRAME|w32.RDW_INVALIDATE|w32.RDW_ALLCHILDREN) +} + +func (w *windowsWebviewWindow) disableRedraw() { + w32.SendMessage(w.hwnd, w32.WM_SETREDRAW, 0, 0) +} + +func (w *windowsWebviewWindow) disableRedrawWithCallback(callback func()) { + w.disableRedraw() + callback() + w.enableRedraw() + +} + +func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) { + var err error + var result w32.HICON + if result = w32.LoadIconWithResourceID(instance, resId); result == 0 { + err = fmt.Errorf("cannot load icon from resource with id %v", resId) + } + return result, err +} + +func (w *windowsWebviewWindow) setMinimiseButtonState(state ButtonState) { + switch state { + case ButtonDisabled, ButtonHidden: + w.setStyle(false, w32.WS_MINIMIZEBOX) + case ButtonEnabled: + w.setStyle(true, w32.WS_SYSMENU) + w.setStyle(true, w32.WS_MINIMIZEBOX) + + } +} + +func (w *windowsWebviewWindow) setMaximiseButtonState(state ButtonState) { + switch state { + case ButtonDisabled, ButtonHidden: + w.setStyle(false, w32.WS_MAXIMIZEBOX) + case ButtonEnabled: + w.setStyle(true, w32.WS_SYSMENU) + w.setStyle(true, w32.WS_MAXIMIZEBOX) + } +} + +func (w *windowsWebviewWindow) setCloseButtonState(state ButtonState) { + switch state { + case ButtonEnabled: + w.setStyle(true, w32.WS_SYSMENU) + _ = w32.EnableCloseButton(w.hwnd) + case ButtonDisabled: + w.setStyle(true, w32.WS_SYSMENU) + _ = w32.DisableCloseButton(w.hwnd) + case ButtonHidden: + w.setStyle(false, w32.WS_SYSMENU) + } +} + +func (w *windowsWebviewWindow) setGWLStyle(style int) { + w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, uint32(style)) +} + +func (w *windowsWebviewWindow) isIgnoreMouseEvents() bool { + exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) + return exStyle&w32.WS_EX_TRANSPARENT != 0 +} + +func (w *windowsWebviewWindow) setIgnoreMouseEvents(ignore bool) { + exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) + if ignore { + exStyle |= w32.WS_EX_LAYERED | w32.WS_EX_TRANSPARENT + } else { + exStyle &^= w32.WS_EX_TRANSPARENT + } + w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(exStyle)) +} + +func (w *windowsWebviewWindow) setPadding(padding edge.Rect) { + // Skip SetPadding if window is being minimized to prevent flickering + if w.isMinimizing { + return + } + w.chromium.SetPadding(padding) +} + +func (w *windowsWebviewWindow) showMenuBar() { + if w.menu != nil { + w32.SetMenu(w.hwnd, w.menu.menu) + } +} + +func (w *windowsWebviewWindow) hideMenuBar() { + if w.menu != nil { + w32.SetMenu(w.hwnd, 0) + } +} diff --git a/v3/pkg/application/webview_window_windows_devtools.go b/v3/pkg/application/webview_window_windows_devtools.go new file mode 100644 index 000000000..e11bebedd --- /dev/null +++ b/v3/pkg/application/webview_window_windows_devtools.go @@ -0,0 +1,16 @@ +//go:build windows && (!production || devtools) + +package application + +import "github.com/wailsapp/go-webview2/pkg/edge" + +func (w *windowsWebviewWindow) openDevTools() { + w.chromium.OpenDevToolsWindow() +} + +func (w *windowsWebviewWindow) enableDevTools(settings *edge.ICoreWebViewSettings) { + err := settings.PutAreDevToolsEnabled(true) + if err != nil { + globalApplication.handleFatalError(err) + } +} diff --git a/v3/pkg/application/webview_window_windows_production.go b/v3/pkg/application/webview_window_windows_production.go new file mode 100644 index 000000000..56ef88b75 --- /dev/null +++ b/v3/pkg/application/webview_window_windows_production.go @@ -0,0 +1,14 @@ +//go:build windows && production && !devtools + +package application + +import "github.com/wailsapp/go-webview2/pkg/edge" + +func (w *windowsWebviewWindow) openDevTools() {} + +func (w *windowsWebviewWindow) enableDevTools(settings *edge.ICoreWebViewSettings) { + err := settings.PutAreDevToolsEnabled(false) + if err != nil { + globalApplication.handleFatalError(err) + } +} diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go new file mode 100644 index 000000000..58b4c4bf5 --- /dev/null +++ b/v3/pkg/application/window.go @@ -0,0 +1,89 @@ +package application + +import ( + "github.com/wailsapp/wails/v3/pkg/events" +) + +type Callback interface { + CallError(callID string, result string, isJSON bool) + CallResponse(callID string, result string) + DialogError(dialogID string, result string) + DialogResponse(dialogID string, result string, isJSON bool) +} + +type Window interface { + Callback + Center() + Close() + DisableSizeConstraints() + DispatchWailsEvent(event *CustomEvent) + EmitEvent(name string, data ...any) + EnableSizeConstraints() + Error(message string, args ...any) + ExecJS(js string) + Focus() + ForceReload() + Fullscreen() Window + GetBorderSizes() *LRTB + GetScreen() (*Screen, error) + GetZoom() float64 + HandleDragAndDropMessage(filenames []string) + HandleMessage(message string) + HandleWindowEvent(id uint) + Height() int + Hide() Window + HideMenuBar() + ID() uint + Info(message string, args ...any) + IsFocused() bool + IsFullscreen() bool + IsIgnoreMouseEvents() bool + IsMaximised() bool + IsMinimised() bool + HandleKeyEvent(acceleratorString string) + Maximise() Window + Minimise() Window + Name() string + OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + OpenContextMenu(data *ContextMenuData) + Position() (int, int) + RelativePosition() (int, int) + Reload() + Resizable() bool + Restore() + Run() + SetPosition(x, y int) + SetAlwaysOnTop(b bool) Window + SetBackgroundColour(colour RGBA) Window + SetFrameless(frameless bool) Window + SetHTML(html string) Window + SetMinimiseButtonState(state ButtonState) Window + SetMaximiseButtonState(state ButtonState) Window + SetCloseButtonState(state ButtonState) Window + SetMaxSize(maxWidth, maxHeight int) Window + SetMinSize(minWidth, minHeight int) Window + SetRelativePosition(x, y int) Window + SetResizable(b bool) Window + SetIgnoreMouseEvents(ignore bool) Window + SetSize(width, height int) Window + SetTitle(title string) Window + SetURL(s string) Window + SetZoom(magnification float64) Window + Show() Window + ShowMenuBar() + Size() (width int, height int) + OpenDevTools() + ToggleFullscreen() + ToggleMaximise() + ToggleMenuBar() + ToggleFrameless() + UnFullscreen() + UnMaximise() + UnMinimise() + Width() int + Zoom() + ZoomIn() + ZoomOut() + ZoomReset() Window + SetMenu(menu *Menu) +} diff --git a/v3/pkg/application/window_manager.go b/v3/pkg/application/window_manager.go new file mode 100644 index 000000000..ec6a3298a --- /dev/null +++ b/v3/pkg/application/window_manager.go @@ -0,0 +1,135 @@ +package application + +// WindowManager manages all window-related operations +type WindowManager struct { + app *App +} + +// newWindowManager creates a new WindowManager instance +func newWindowManager(app *App) *WindowManager { + return &WindowManager{ + app: app, + // Use the app's existing windows map - don't create a new one + } +} + +// GetByName returns a window by name and whether it exists +func (wm *WindowManager) GetByName(name string) (Window, bool) { + wm.app.windowsLock.RLock() + defer wm.app.windowsLock.RUnlock() + + for _, window := range wm.app.windows { + if window.Name() == name { + return window, true + } + } + return nil, false +} + +// Get is an alias for GetByName for consistency +func (wm *WindowManager) Get(name string) (Window, bool) { + return wm.GetByName(name) +} + +// GetByID returns a window by ID and whether it exists +func (wm *WindowManager) GetByID(id uint) (Window, bool) { + wm.app.windowsLock.RLock() + defer wm.app.windowsLock.RUnlock() + + window, exists := wm.app.windows[id] + return window, exists +} + +// OnCreate registers a callback to be called when a window is created +func (wm *WindowManager) OnCreate(callback func(Window)) { + wm.app.windowCreatedCallbacks = append(wm.app.windowCreatedCallbacks, callback) +} + +// New creates a new webview window +func (wm *WindowManager) New() *WebviewWindow { + return wm.NewWithOptions(WebviewWindowOptions{}) +} + +// NewWithOptions creates a new webview window with options +func (wm *WindowManager) NewWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow { + newWindow := NewWindow(windowOptions) + id := newWindow.ID() + + wm.app.windowsLock.Lock() + wm.app.windows[id] = newWindow + wm.app.windowsLock.Unlock() + + // Call hooks + for _, hook := range wm.app.windowCreatedCallbacks { + hook(newWindow) + } + + wm.app.runOrDeferToAppRun(newWindow) + + return newWindow +} + +// Current returns the current active window (may be nil) +func (wm *WindowManager) Current() *WebviewWindow { + if wm.app.impl == nil { + return nil + } + id := wm.app.impl.getCurrentWindowID() + wm.app.windowsLock.RLock() + defer wm.app.windowsLock.RUnlock() + result := wm.app.windows[id] + if result == nil { + return nil + } + return result.(*WebviewWindow) +} + +// Add adds a window to the manager +func (wm *WindowManager) Add(window Window) { + wm.app.windowsLock.Lock() + defer wm.app.windowsLock.Unlock() + wm.app.windows[window.ID()] = window + + // Call registered callbacks + for _, callback := range wm.app.windowCreatedCallbacks { + callback(window) + } +} + +// Remove removes a window from the manager by ID +func (wm *WindowManager) Remove(windowID uint) { + wm.app.windowsLock.Lock() + defer wm.app.windowsLock.Unlock() + delete(wm.app.windows, windowID) +} + +// RemoveByName removes a window from the manager by name +func (wm *WindowManager) RemoveByName(name string) bool { + window, exists := wm.GetByName(name) + if exists { + wm.Remove(window.ID()) + return true + } + return false +} + +// Internal methods for backward compatibility +func (wm *WindowManager) add(window Window) { + wm.Add(window) +} + +func (wm *WindowManager) remove(windowID uint) { + wm.Remove(windowID) +} + +// GetAll returns all windows +func (wm *WindowManager) GetAll() []Window { + wm.app.windowsLock.RLock() + defer wm.app.windowsLock.RUnlock() + + windows := make([]Window, 0, len(wm.app.windows)) + for _, window := range wm.app.windows { + windows = append(windows, window) + } + return windows +} diff --git a/v3/pkg/events/README.md b/v3/pkg/events/README.md new file mode 100644 index 000000000..f9389f198 --- /dev/null +++ b/v3/pkg/events/README.md @@ -0,0 +1,14 @@ +# Events + +This package is used to generate the event management code and to allow quick addition of events. + +## Usage + +1. Add events to `events.txt` +2. Run `task generate:events` + +## Notes + +For events that you want to handle manually, add a `!` to the end of the event name and +add custom code into the appropriate platform. See [this PR](https://github.com/wailsapp/wails/pull/2991) +for an example of how to do this. \ No newline at end of file diff --git a/v3/pkg/events/defaults.go b/v3/pkg/events/defaults.go new file mode 100644 index 000000000..376bef36f --- /dev/null +++ b/v3/pkg/events/defaults.go @@ -0,0 +1,58 @@ +package events + +import "runtime" + +var defaultWindowEventMapping = map[string]map[WindowEventType]WindowEventType{ + "windows": { + Windows.WindowClosing: Common.WindowClosing, + Windows.WindowInactive: Common.WindowLostFocus, + Windows.WindowClickActive: Common.WindowFocus, + Windows.WindowActive: Common.WindowFocus, + Windows.WindowMaximise: Common.WindowMaximise, + Windows.WindowMinimise: Common.WindowMinimise, + Windows.WindowRestore: Common.WindowRestore, + Windows.WindowUnMaximise: Common.WindowUnMaximise, + Windows.WindowUnMinimise: Common.WindowUnMinimise, + Windows.WindowFullscreen: Common.WindowFullscreen, + Windows.WindowUnFullscreen: Common.WindowUnFullscreen, + Windows.WindowShow: Common.WindowShow, + Windows.WindowHide: Common.WindowHide, + Windows.WindowDidMove: Common.WindowDidMove, + Windows.WindowDidResize: Common.WindowDidResize, + Windows.WindowSetFocus: Common.WindowFocus, + Windows.WindowKillFocus: Common.WindowLostFocus, + Windows.WindowDPIChanged: Common.WindowDPIChanged, + }, + "darwin": { + Mac.WindowDidResignKey: Common.WindowLostFocus, + Mac.WindowDidBecomeKey: Common.WindowFocus, + Mac.WindowDidMiniaturize: Common.WindowMinimise, + Mac.WindowDidDeminiaturize: Common.WindowUnMinimise, + Mac.WindowDidEnterFullScreen: Common.WindowFullscreen, + Mac.WindowDidExitFullScreen: Common.WindowUnFullscreen, + Mac.WindowMaximise: Common.WindowMaximise, + Mac.WindowUnMaximise: Common.WindowUnMaximise, + Mac.WindowDidMove: Common.WindowDidMove, + Mac.WindowDidResize: Common.WindowDidResize, + Mac.WindowDidZoom: Common.WindowMaximise, + Mac.WindowShow: Common.WindowShow, + Mac.WindowHide: Common.WindowHide, + Mac.WindowZoomIn: Common.WindowZoomIn, + Mac.WindowZoomOut: Common.WindowZoomOut, + Mac.WindowZoomReset: Common.WindowZoomReset, + Mac.WindowShouldClose: Common.WindowClosing, + }, + "linux": { + Linux.WindowDeleteEvent: Common.WindowClosing, + Linux.WindowFocusIn: Common.WindowFocus, + Linux.WindowFocusOut: Common.WindowLostFocus, + Linux.WindowDidMove: Common.WindowDidMove, + Linux.WindowDidResize: Common.WindowDidResize, + Linux.WindowLoadChanged: Common.WindowShow, + }, +} + +func DefaultWindowEventMapping() map[WindowEventType]WindowEventType { + platform := runtime.GOOS + return defaultWindowEventMapping[platform] +} diff --git a/v3/pkg/events/events.go b/v3/pkg/events/events.go new file mode 100644 index 000000000..c0d200103 --- /dev/null +++ b/v3/pkg/events/events.go @@ -0,0 +1,679 @@ +package events + +type ApplicationEventType uint +type WindowEventType uint + +var Common = newCommonEvents() + +type commonEvents struct { + ApplicationOpenedWithFile ApplicationEventType + ApplicationStarted ApplicationEventType + ApplicationLaunchedWithUrl ApplicationEventType + ThemeChanged ApplicationEventType + WindowClosing WindowEventType + WindowDidMove WindowEventType + WindowDidResize WindowEventType + WindowDPIChanged WindowEventType + WindowFilesDropped WindowEventType + WindowFocus WindowEventType + WindowFullscreen WindowEventType + WindowHide WindowEventType + WindowLostFocus WindowEventType + WindowMaximise WindowEventType + WindowMinimise WindowEventType + WindowRestore WindowEventType + WindowRuntimeReady WindowEventType + WindowShow WindowEventType + WindowUnFullscreen WindowEventType + WindowUnMaximise WindowEventType + WindowUnMinimise WindowEventType + WindowZoom WindowEventType + WindowZoomIn WindowEventType + WindowZoomOut WindowEventType + WindowZoomReset WindowEventType +} + +func newCommonEvents() commonEvents { + return commonEvents{ + ApplicationOpenedWithFile: 1024, + ApplicationStarted: 1025, + ApplicationLaunchedWithUrl: 1026, + ThemeChanged: 1027, + WindowClosing: 1028, + WindowDidMove: 1029, + WindowDidResize: 1030, + WindowDPIChanged: 1031, + WindowFilesDropped: 1032, + WindowFocus: 1033, + WindowFullscreen: 1034, + WindowHide: 1035, + WindowLostFocus: 1036, + WindowMaximise: 1037, + WindowMinimise: 1038, + WindowRestore: 1039, + WindowRuntimeReady: 1040, + WindowShow: 1041, + WindowUnFullscreen: 1042, + WindowUnMaximise: 1043, + WindowUnMinimise: 1044, + WindowZoom: 1045, + WindowZoomIn: 1046, + WindowZoomOut: 1047, + WindowZoomReset: 1048, + } +} + +var Linux = newLinuxEvents() + +type linuxEvents struct { + ApplicationStartup ApplicationEventType + SystemThemeChanged ApplicationEventType + WindowDeleteEvent WindowEventType + WindowDidMove WindowEventType + WindowDidResize WindowEventType + WindowFocusIn WindowEventType + WindowFocusOut WindowEventType + WindowLoadChanged WindowEventType +} + +func newLinuxEvents() linuxEvents { + return linuxEvents{ + ApplicationStartup: 1049, + SystemThemeChanged: 1050, + WindowDeleteEvent: 1051, + WindowDidMove: 1052, + WindowDidResize: 1053, + WindowFocusIn: 1054, + WindowFocusOut: 1055, + WindowLoadChanged: 1056, + } +} + +var Mac = newMacEvents() + +type macEvents struct { + ApplicationDidBecomeActive ApplicationEventType + ApplicationDidChangeBackingProperties ApplicationEventType + ApplicationDidChangeEffectiveAppearance ApplicationEventType + ApplicationDidChangeIcon ApplicationEventType + ApplicationDidChangeOcclusionState ApplicationEventType + ApplicationDidChangeScreenParameters ApplicationEventType + ApplicationDidChangeStatusBarFrame ApplicationEventType + ApplicationDidChangeStatusBarOrientation ApplicationEventType + ApplicationDidChangeTheme ApplicationEventType + ApplicationDidFinishLaunching ApplicationEventType + ApplicationDidHide ApplicationEventType + ApplicationDidResignActive ApplicationEventType + ApplicationDidUnhide ApplicationEventType + ApplicationDidUpdate ApplicationEventType + ApplicationShouldHandleReopen ApplicationEventType + ApplicationWillBecomeActive ApplicationEventType + ApplicationWillFinishLaunching ApplicationEventType + ApplicationWillHide ApplicationEventType + ApplicationWillResignActive ApplicationEventType + ApplicationWillTerminate ApplicationEventType + ApplicationWillUnhide ApplicationEventType + ApplicationWillUpdate ApplicationEventType + MenuDidAddItem ApplicationEventType + MenuDidBeginTracking ApplicationEventType + MenuDidClose ApplicationEventType + MenuDidDisplayItem ApplicationEventType + MenuDidEndTracking ApplicationEventType + MenuDidHighlightItem ApplicationEventType + MenuDidOpen ApplicationEventType + MenuDidPopUp ApplicationEventType + MenuDidRemoveItem ApplicationEventType + MenuDidSendAction ApplicationEventType + MenuDidSendActionToItem ApplicationEventType + MenuDidUpdate ApplicationEventType + MenuWillAddItem ApplicationEventType + MenuWillBeginTracking ApplicationEventType + MenuWillDisplayItem ApplicationEventType + MenuWillEndTracking ApplicationEventType + MenuWillHighlightItem ApplicationEventType + MenuWillOpen ApplicationEventType + MenuWillPopUp ApplicationEventType + MenuWillRemoveItem ApplicationEventType + MenuWillSendAction ApplicationEventType + MenuWillSendActionToItem ApplicationEventType + MenuWillUpdate ApplicationEventType + WebViewDidCommitNavigation WindowEventType + WebViewDidFinishNavigation WindowEventType + WebViewDidReceiveServerRedirectForProvisionalNavigation WindowEventType + WebViewDidStartProvisionalNavigation WindowEventType + WindowDidBecomeKey WindowEventType + WindowDidBecomeMain WindowEventType + WindowDidBeginSheet WindowEventType + WindowDidChangeAlpha WindowEventType + WindowDidChangeBackingLocation WindowEventType + WindowDidChangeBackingProperties WindowEventType + WindowDidChangeCollectionBehavior WindowEventType + WindowDidChangeEffectiveAppearance WindowEventType + WindowDidChangeOcclusionState WindowEventType + WindowDidChangeOrderingMode WindowEventType + WindowDidChangeScreen WindowEventType + WindowDidChangeScreenParameters WindowEventType + WindowDidChangeScreenProfile WindowEventType + WindowDidChangeScreenSpace WindowEventType + WindowDidChangeScreenSpaceProperties WindowEventType + WindowDidChangeSharingType WindowEventType + WindowDidChangeSpace WindowEventType + WindowDidChangeSpaceOrderingMode WindowEventType + WindowDidChangeTitle WindowEventType + WindowDidChangeToolbar WindowEventType + WindowDidDeminiaturize WindowEventType + WindowDidEndSheet WindowEventType + WindowDidEnterFullScreen WindowEventType + WindowDidEnterVersionBrowser WindowEventType + WindowDidExitFullScreen WindowEventType + WindowDidExitVersionBrowser WindowEventType + WindowDidExpose WindowEventType + WindowDidFocus WindowEventType + WindowDidMiniaturize WindowEventType + WindowDidMove WindowEventType + WindowDidOrderOffScreen WindowEventType + WindowDidOrderOnScreen WindowEventType + WindowDidResignKey WindowEventType + WindowDidResignMain WindowEventType + WindowDidResize WindowEventType + WindowDidUpdate WindowEventType + WindowDidUpdateAlpha WindowEventType + WindowDidUpdateCollectionBehavior WindowEventType + WindowDidUpdateCollectionProperties WindowEventType + WindowDidUpdateShadow WindowEventType + WindowDidUpdateTitle WindowEventType + WindowDidUpdateToolbar WindowEventType + WindowDidZoom WindowEventType + WindowFileDraggingEntered WindowEventType + WindowFileDraggingExited WindowEventType + WindowFileDraggingPerformed WindowEventType + WindowHide WindowEventType + WindowMaximise WindowEventType + WindowUnMaximise WindowEventType + WindowMinimise WindowEventType + WindowUnMinimise WindowEventType + WindowShouldClose WindowEventType + WindowShow WindowEventType + WindowWillBecomeKey WindowEventType + WindowWillBecomeMain WindowEventType + WindowWillBeginSheet WindowEventType + WindowWillChangeOrderingMode WindowEventType + WindowWillClose WindowEventType + WindowWillDeminiaturize WindowEventType + WindowWillEnterFullScreen WindowEventType + WindowWillEnterVersionBrowser WindowEventType + WindowWillExitFullScreen WindowEventType + WindowWillExitVersionBrowser WindowEventType + WindowWillFocus WindowEventType + WindowWillMiniaturize WindowEventType + WindowWillMove WindowEventType + WindowWillOrderOffScreen WindowEventType + WindowWillOrderOnScreen WindowEventType + WindowWillResignMain WindowEventType + WindowWillResize WindowEventType + WindowWillUnfocus WindowEventType + WindowWillUpdate WindowEventType + WindowWillUpdateAlpha WindowEventType + WindowWillUpdateCollectionBehavior WindowEventType + WindowWillUpdateCollectionProperties WindowEventType + WindowWillUpdateShadow WindowEventType + WindowWillUpdateTitle WindowEventType + WindowWillUpdateToolbar WindowEventType + WindowWillUpdateVisibility WindowEventType + WindowWillUseStandardFrame WindowEventType + WindowZoomIn WindowEventType + WindowZoomOut WindowEventType + WindowZoomReset WindowEventType +} + +func newMacEvents() macEvents { + return macEvents{ + ApplicationDidBecomeActive: 1057, + ApplicationDidChangeBackingProperties: 1058, + ApplicationDidChangeEffectiveAppearance: 1059, + ApplicationDidChangeIcon: 1060, + ApplicationDidChangeOcclusionState: 1061, + ApplicationDidChangeScreenParameters: 1062, + ApplicationDidChangeStatusBarFrame: 1063, + ApplicationDidChangeStatusBarOrientation: 1064, + ApplicationDidChangeTheme: 1065, + ApplicationDidFinishLaunching: 1066, + ApplicationDidHide: 1067, + ApplicationDidResignActive: 1068, + ApplicationDidUnhide: 1069, + ApplicationDidUpdate: 1070, + ApplicationShouldHandleReopen: 1071, + ApplicationWillBecomeActive: 1072, + ApplicationWillFinishLaunching: 1073, + ApplicationWillHide: 1074, + ApplicationWillResignActive: 1075, + ApplicationWillTerminate: 1076, + ApplicationWillUnhide: 1077, + ApplicationWillUpdate: 1078, + MenuDidAddItem: 1079, + MenuDidBeginTracking: 1080, + MenuDidClose: 1081, + MenuDidDisplayItem: 1082, + MenuDidEndTracking: 1083, + MenuDidHighlightItem: 1084, + MenuDidOpen: 1085, + MenuDidPopUp: 1086, + MenuDidRemoveItem: 1087, + MenuDidSendAction: 1088, + MenuDidSendActionToItem: 1089, + MenuDidUpdate: 1090, + MenuWillAddItem: 1091, + MenuWillBeginTracking: 1092, + MenuWillDisplayItem: 1093, + MenuWillEndTracking: 1094, + MenuWillHighlightItem: 1095, + MenuWillOpen: 1096, + MenuWillPopUp: 1097, + MenuWillRemoveItem: 1098, + MenuWillSendAction: 1099, + MenuWillSendActionToItem: 1100, + MenuWillUpdate: 1101, + WebViewDidCommitNavigation: 1102, + WebViewDidFinishNavigation: 1103, + WebViewDidReceiveServerRedirectForProvisionalNavigation: 1104, + WebViewDidStartProvisionalNavigation: 1105, + WindowDidBecomeKey: 1106, + WindowDidBecomeMain: 1107, + WindowDidBeginSheet: 1108, + WindowDidChangeAlpha: 1109, + WindowDidChangeBackingLocation: 1110, + WindowDidChangeBackingProperties: 1111, + WindowDidChangeCollectionBehavior: 1112, + WindowDidChangeEffectiveAppearance: 1113, + WindowDidChangeOcclusionState: 1114, + WindowDidChangeOrderingMode: 1115, + WindowDidChangeScreen: 1116, + WindowDidChangeScreenParameters: 1117, + WindowDidChangeScreenProfile: 1118, + WindowDidChangeScreenSpace: 1119, + WindowDidChangeScreenSpaceProperties: 1120, + WindowDidChangeSharingType: 1121, + WindowDidChangeSpace: 1122, + WindowDidChangeSpaceOrderingMode: 1123, + WindowDidChangeTitle: 1124, + WindowDidChangeToolbar: 1125, + WindowDidDeminiaturize: 1126, + WindowDidEndSheet: 1127, + WindowDidEnterFullScreen: 1128, + WindowDidEnterVersionBrowser: 1129, + WindowDidExitFullScreen: 1130, + WindowDidExitVersionBrowser: 1131, + WindowDidExpose: 1132, + WindowDidFocus: 1133, + WindowDidMiniaturize: 1134, + WindowDidMove: 1135, + WindowDidOrderOffScreen: 1136, + WindowDidOrderOnScreen: 1137, + WindowDidResignKey: 1138, + WindowDidResignMain: 1139, + WindowDidResize: 1140, + WindowDidUpdate: 1141, + WindowDidUpdateAlpha: 1142, + WindowDidUpdateCollectionBehavior: 1143, + WindowDidUpdateCollectionProperties: 1144, + WindowDidUpdateShadow: 1145, + WindowDidUpdateTitle: 1146, + WindowDidUpdateToolbar: 1147, + WindowDidZoom: 1148, + WindowFileDraggingEntered: 1149, + WindowFileDraggingExited: 1150, + WindowFileDraggingPerformed: 1151, + WindowHide: 1152, + WindowMaximise: 1153, + WindowUnMaximise: 1154, + WindowMinimise: 1155, + WindowUnMinimise: 1156, + WindowShouldClose: 1157, + WindowShow: 1158, + WindowWillBecomeKey: 1159, + WindowWillBecomeMain: 1160, + WindowWillBeginSheet: 1161, + WindowWillChangeOrderingMode: 1162, + WindowWillClose: 1163, + WindowWillDeminiaturize: 1164, + WindowWillEnterFullScreen: 1165, + WindowWillEnterVersionBrowser: 1166, + WindowWillExitFullScreen: 1167, + WindowWillExitVersionBrowser: 1168, + WindowWillFocus: 1169, + WindowWillMiniaturize: 1170, + WindowWillMove: 1171, + WindowWillOrderOffScreen: 1172, + WindowWillOrderOnScreen: 1173, + WindowWillResignMain: 1174, + WindowWillResize: 1175, + WindowWillUnfocus: 1176, + WindowWillUpdate: 1177, + WindowWillUpdateAlpha: 1178, + WindowWillUpdateCollectionBehavior: 1179, + WindowWillUpdateCollectionProperties: 1180, + WindowWillUpdateShadow: 1181, + WindowWillUpdateTitle: 1182, + WindowWillUpdateToolbar: 1183, + WindowWillUpdateVisibility: 1184, + WindowWillUseStandardFrame: 1185, + WindowZoomIn: 1186, + WindowZoomOut: 1187, + WindowZoomReset: 1188, + } +} + +var Windows = newWindowsEvents() + +type windowsEvents struct { + APMPowerSettingChange ApplicationEventType + APMPowerStatusChange ApplicationEventType + APMResumeAutomatic ApplicationEventType + APMResumeSuspend ApplicationEventType + APMSuspend ApplicationEventType + ApplicationStarted ApplicationEventType + SystemThemeChanged ApplicationEventType + WebViewNavigationCompleted WindowEventType + WindowActive WindowEventType + WindowBackgroundErase WindowEventType + WindowClickActive WindowEventType + WindowClosing WindowEventType + WindowDidMove WindowEventType + WindowDidResize WindowEventType + WindowDPIChanged WindowEventType + WindowDragDrop WindowEventType + WindowDragEnter WindowEventType + WindowDragLeave WindowEventType + WindowDragOver WindowEventType + WindowEndMove WindowEventType + WindowEndResize WindowEventType + WindowFullscreen WindowEventType + WindowHide WindowEventType + WindowInactive WindowEventType + WindowKeyDown WindowEventType + WindowKeyUp WindowEventType + WindowKillFocus WindowEventType + WindowNonClientHit WindowEventType + WindowNonClientMouseDown WindowEventType + WindowNonClientMouseLeave WindowEventType + WindowNonClientMouseMove WindowEventType + WindowNonClientMouseUp WindowEventType + WindowPaint WindowEventType + WindowRestore WindowEventType + WindowSetFocus WindowEventType + WindowShow WindowEventType + WindowStartMove WindowEventType + WindowStartResize WindowEventType + WindowUnFullscreen WindowEventType + WindowZOrderChanged WindowEventType + WindowMinimise WindowEventType + WindowUnMinimise WindowEventType + WindowMaximise WindowEventType + WindowUnMaximise WindowEventType +} + +func newWindowsEvents() windowsEvents { + return windowsEvents{ + APMPowerSettingChange: 1189, + APMPowerStatusChange: 1190, + APMResumeAutomatic: 1191, + APMResumeSuspend: 1192, + APMSuspend: 1193, + ApplicationStarted: 1194, + SystemThemeChanged: 1195, + WebViewNavigationCompleted: 1196, + WindowActive: 1197, + WindowBackgroundErase: 1198, + WindowClickActive: 1199, + WindowClosing: 1200, + WindowDidMove: 1201, + WindowDidResize: 1202, + WindowDPIChanged: 1203, + WindowDragDrop: 1204, + WindowDragEnter: 1205, + WindowDragLeave: 1206, + WindowDragOver: 1207, + WindowEndMove: 1208, + WindowEndResize: 1209, + WindowFullscreen: 1210, + WindowHide: 1211, + WindowInactive: 1212, + WindowKeyDown: 1213, + WindowKeyUp: 1214, + WindowKillFocus: 1215, + WindowNonClientHit: 1216, + WindowNonClientMouseDown: 1217, + WindowNonClientMouseLeave: 1218, + WindowNonClientMouseMove: 1219, + WindowNonClientMouseUp: 1220, + WindowPaint: 1221, + WindowRestore: 1222, + WindowSetFocus: 1223, + WindowShow: 1224, + WindowStartMove: 1225, + WindowStartResize: 1226, + WindowUnFullscreen: 1227, + WindowZOrderChanged: 1228, + WindowMinimise: 1229, + WindowUnMinimise: 1230, + WindowMaximise: 1231, + WindowUnMaximise: 1232, + } +} + +func JSEvent(event uint) string { + return eventToJS[event] +} + +var eventToJS = map[uint]string{ + 1024: "ApplicationOpenedWithFile", + 1025: "ApplicationStarted", + 1026: "ApplicationLaunchedWithUrl", + 1027: "ThemeChanged", + 1028: "WindowClosing", + 1029: "WindowDidMove", + 1030: "WindowDidResize", + 1031: "WindowDPIChanged", + 1032: "WindowFilesDropped", + 1033: "WindowFocus", + 1034: "WindowFullscreen", + 1035: "WindowHide", + 1036: "WindowLostFocus", + 1037: "WindowMaximise", + 1038: "WindowMinimise", + 1039: "WindowRestore", + 1040: "WindowRuntimeReady", + 1041: "WindowShow", + 1042: "WindowUnFullscreen", + 1043: "WindowUnMaximise", + 1044: "WindowUnMinimise", + 1045: "WindowZoom", + 1046: "WindowZoomIn", + 1047: "WindowZoomOut", + 1048: "WindowZoomReset", + 1049: "ApplicationStartup", + 1050: "SystemThemeChanged", + 1051: "WindowDeleteEvent", + 1052: "WindowDidMove", + 1053: "WindowDidResize", + 1054: "WindowFocusIn", + 1055: "WindowFocusOut", + 1056: "WindowLoadChanged", + 1057: "ApplicationDidBecomeActive", + 1058: "ApplicationDidChangeBackingProperties", + 1059: "ApplicationDidChangeEffectiveAppearance", + 1060: "ApplicationDidChangeIcon", + 1061: "ApplicationDidChangeOcclusionState", + 1062: "ApplicationDidChangeScreenParameters", + 1063: "ApplicationDidChangeStatusBarFrame", + 1064: "ApplicationDidChangeStatusBarOrientation", + 1065: "ApplicationDidChangeTheme", + 1066: "ApplicationDidFinishLaunching", + 1067: "ApplicationDidHide", + 1068: "ApplicationDidResignActive", + 1069: "ApplicationDidUnhide", + 1070: "ApplicationDidUpdate", + 1071: "ApplicationShouldHandleReopen", + 1072: "ApplicationWillBecomeActive", + 1073: "ApplicationWillFinishLaunching", + 1074: "ApplicationWillHide", + 1075: "ApplicationWillResignActive", + 1076: "ApplicationWillTerminate", + 1077: "ApplicationWillUnhide", + 1078: "ApplicationWillUpdate", + 1079: "MenuDidAddItem", + 1080: "MenuDidBeginTracking", + 1081: "MenuDidClose", + 1082: "MenuDidDisplayItem", + 1083: "MenuDidEndTracking", + 1084: "MenuDidHighlightItem", + 1085: "MenuDidOpen", + 1086: "MenuDidPopUp", + 1087: "MenuDidRemoveItem", + 1088: "MenuDidSendAction", + 1089: "MenuDidSendActionToItem", + 1090: "MenuDidUpdate", + 1091: "MenuWillAddItem", + 1092: "MenuWillBeginTracking", + 1093: "MenuWillDisplayItem", + 1094: "MenuWillEndTracking", + 1095: "MenuWillHighlightItem", + 1096: "MenuWillOpen", + 1097: "MenuWillPopUp", + 1098: "MenuWillRemoveItem", + 1099: "MenuWillSendAction", + 1100: "MenuWillSendActionToItem", + 1101: "MenuWillUpdate", + 1102: "WebViewDidCommitNavigation", + 1103: "WebViewDidFinishNavigation", + 1104: "WebViewDidReceiveServerRedirectForProvisionalNavigation", + 1105: "WebViewDidStartProvisionalNavigation", + 1106: "WindowDidBecomeKey", + 1107: "WindowDidBecomeMain", + 1108: "WindowDidBeginSheet", + 1109: "WindowDidChangeAlpha", + 1110: "WindowDidChangeBackingLocation", + 1111: "WindowDidChangeBackingProperties", + 1112: "WindowDidChangeCollectionBehavior", + 1113: "WindowDidChangeEffectiveAppearance", + 1114: "WindowDidChangeOcclusionState", + 1115: "WindowDidChangeOrderingMode", + 1116: "WindowDidChangeScreen", + 1117: "WindowDidChangeScreenParameters", + 1118: "WindowDidChangeScreenProfile", + 1119: "WindowDidChangeScreenSpace", + 1120: "WindowDidChangeScreenSpaceProperties", + 1121: "WindowDidChangeSharingType", + 1122: "WindowDidChangeSpace", + 1123: "WindowDidChangeSpaceOrderingMode", + 1124: "WindowDidChangeTitle", + 1125: "WindowDidChangeToolbar", + 1126: "WindowDidDeminiaturize", + 1127: "WindowDidEndSheet", + 1128: "WindowDidEnterFullScreen", + 1129: "WindowDidEnterVersionBrowser", + 1130: "WindowDidExitFullScreen", + 1131: "WindowDidExitVersionBrowser", + 1132: "WindowDidExpose", + 1133: "WindowDidFocus", + 1134: "WindowDidMiniaturize", + 1135: "WindowDidMove", + 1136: "WindowDidOrderOffScreen", + 1137: "WindowDidOrderOnScreen", + 1138: "WindowDidResignKey", + 1139: "WindowDidResignMain", + 1140: "WindowDidResize", + 1141: "WindowDidUpdate", + 1142: "WindowDidUpdateAlpha", + 1143: "WindowDidUpdateCollectionBehavior", + 1144: "WindowDidUpdateCollectionProperties", + 1145: "WindowDidUpdateShadow", + 1146: "WindowDidUpdateTitle", + 1147: "WindowDidUpdateToolbar", + 1148: "WindowDidZoom", + 1149: "WindowFileDraggingEntered", + 1150: "WindowFileDraggingExited", + 1151: "WindowFileDraggingPerformed", + 1152: "WindowHide", + 1153: "WindowMaximise", + 1154: "WindowUnMaximise", + 1155: "WindowMinimise", + 1156: "WindowUnMinimise", + 1157: "WindowShouldClose", + 1158: "WindowShow", + 1159: "WindowWillBecomeKey", + 1160: "WindowWillBecomeMain", + 1161: "WindowWillBeginSheet", + 1162: "WindowWillChangeOrderingMode", + 1163: "WindowWillClose", + 1164: "WindowWillDeminiaturize", + 1165: "WindowWillEnterFullScreen", + 1166: "WindowWillEnterVersionBrowser", + 1167: "WindowWillExitFullScreen", + 1168: "WindowWillExitVersionBrowser", + 1169: "WindowWillFocus", + 1170: "WindowWillMiniaturize", + 1171: "WindowWillMove", + 1172: "WindowWillOrderOffScreen", + 1173: "WindowWillOrderOnScreen", + 1174: "WindowWillResignMain", + 1175: "WindowWillResize", + 1176: "WindowWillUnfocus", + 1177: "WindowWillUpdate", + 1178: "WindowWillUpdateAlpha", + 1179: "WindowWillUpdateCollectionBehavior", + 1180: "WindowWillUpdateCollectionProperties", + 1181: "WindowWillUpdateShadow", + 1182: "WindowWillUpdateTitle", + 1183: "WindowWillUpdateToolbar", + 1184: "WindowWillUpdateVisibility", + 1185: "WindowWillUseStandardFrame", + 1186: "WindowZoomIn", + 1187: "WindowZoomOut", + 1188: "WindowZoomReset", + 1189: "APMPowerSettingChange", + 1190: "APMPowerStatusChange", + 1191: "APMResumeAutomatic", + 1192: "APMResumeSuspend", + 1193: "APMSuspend", + 1194: "ApplicationStarted", + 1195: "SystemThemeChanged", + 1196: "WebViewNavigationCompleted", + 1197: "WindowActive", + 1198: "WindowBackgroundErase", + 1199: "WindowClickActive", + 1200: "WindowClosing", + 1201: "WindowDidMove", + 1202: "WindowDidResize", + 1203: "WindowDPIChanged", + 1204: "WindowDragDrop", + 1205: "WindowDragEnter", + 1206: "WindowDragLeave", + 1207: "WindowDragOver", + 1208: "WindowEndMove", + 1209: "WindowEndResize", + 1210: "WindowFullscreen", + 1211: "WindowHide", + 1212: "WindowInactive", + 1213: "WindowKeyDown", + 1214: "WindowKeyUp", + 1215: "WindowKillFocus", + 1216: "WindowNonClientHit", + 1217: "WindowNonClientMouseDown", + 1218: "WindowNonClientMouseLeave", + 1219: "WindowNonClientMouseMove", + 1220: "WindowNonClientMouseUp", + 1221: "WindowPaint", + 1222: "WindowRestore", + 1223: "WindowSetFocus", + 1224: "WindowShow", + 1225: "WindowStartMove", + 1226: "WindowStartResize", + 1227: "WindowUnFullscreen", + 1228: "WindowZOrderChanged", + 1229: "WindowMinimise", + 1230: "WindowUnMinimise", + 1231: "WindowMaximise", + 1232: "WindowUnMaximise", +} + diff --git a/v3/pkg/events/events.h b/v3/pkg/events/events.h new file mode 100644 index 000000000..12a66b2a3 --- /dev/null +++ b/v3/pkg/events/events.h @@ -0,0 +1,138 @@ +//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationDidBecomeActive 1025 +#define EventApplicationDidChangeBackingProperties 1026 +#define EventApplicationDidChangeEffectiveAppearance 1027 +#define EventApplicationDidChangeIcon 1028 +#define EventApplicationDidChangeOcclusionState 1029 +#define EventApplicationDidChangeScreenParameters 1030 +#define EventApplicationDidChangeStatusBarFrame 1031 +#define EventApplicationDidChangeStatusBarOrientation 1032 +#define EventApplicationDidFinishLaunching 1033 +#define EventApplicationDidHide 1034 +#define EventApplicationDidResignActiveNotification 1035 +#define EventApplicationDidUnhide 1036 +#define EventApplicationDidUpdate 1037 +#define EventApplicationWillBecomeActive 1038 +#define EventApplicationWillFinishLaunching 1039 +#define EventApplicationWillHide 1040 +#define EventApplicationWillResignActive 1041 +#define EventApplicationWillTerminate 1042 +#define EventApplicationWillUnhide 1043 +#define EventApplicationWillUpdate 1044 +#define EventApplicationTerminate 1045 +#define EventApplicationDidChangeTheme 1046 +#define EventApplicationShouldHandleReopen 1047 +#define EventWindowDidBecomeKey 1048 +#define EventWindowDidBecomeMain 1049 +#define EventWindowDidBeginSheet 1050 +#define EventWindowDidChangeAlpha 1051 +#define EventWindowDidChangeBackingLocation 1052 +#define EventWindowDidChangeBackingProperties 1053 +#define EventWindowDidChangeCollectionBehavior 1054 +#define EventWindowDidChangeEffectiveAppearance 1055 +#define EventWindowDidChangeOcclusionState 1056 +#define EventWindowDidChangeOrderingMode 1057 +#define EventWindowDidChangeScreen 1058 +#define EventWindowDidChangeScreenParameters 1059 +#define EventWindowDidChangeScreenProfile 1060 +#define EventWindowDidChangeScreenSpace 1061 +#define EventWindowDidChangeScreenSpaceProperties 1062 +#define EventWindowDidChangeSharingType 1063 +#define EventWindowDidChangeSpace 1064 +#define EventWindowDidChangeSpaceOrderingMode 1065 +#define EventWindowDidChangeTitle 1066 +#define EventWindowDidChangeToolbar 1067 +#define EventWindowDidChangeVisibility 1068 +#define EventWindowDidDeminiaturize 1069 +#define EventWindowDidEndSheet 1070 +#define EventWindowDidEnterFullScreen 1071 +#define EventWindowDidEnterVersionBrowser 1072 +#define EventWindowDidExitFullScreen 1073 +#define EventWindowDidExitVersionBrowser 1074 +#define EventWindowDidExpose 1075 +#define EventWindowDidFocus 1076 +#define EventWindowDidMiniaturize 1077 +#define EventWindowDidMove 1078 +#define EventWindowDidOrderOffScreen 1079 +#define EventWindowDidOrderOnScreen 1080 +#define EventWindowDidResignKey 1081 +#define EventWindowDidResignMain 1082 +#define EventWindowDidResize 1083 +#define EventWindowDidUpdate 1084 +#define EventWindowDidUpdateAlpha 1085 +#define EventWindowDidUpdateCollectionBehavior 1086 +#define EventWindowDidUpdateCollectionProperties 1087 +#define EventWindowDidUpdateShadow 1088 +#define EventWindowDidUpdateTitle 1089 +#define EventWindowDidUpdateToolbar 1090 +#define EventWindowDidUpdateVisibility 1091 +#define EventWindowShouldClose 1092 +#define EventWindowWillBecomeKey 1093 +#define EventWindowWillBecomeMain 1094 +#define EventWindowWillBeginSheet 1095 +#define EventWindowWillChangeOrderingMode 1096 +#define EventWindowWillClose 1097 +#define EventWindowWillDeminiaturize 1098 +#define EventWindowWillEnterFullScreen 1099 +#define EventWindowWillEnterVersionBrowser 1100 +#define EventWindowWillExitFullScreen 1101 +#define EventWindowWillExitVersionBrowser 1102 +#define EventWindowWillFocus 1103 +#define EventWindowWillMiniaturize 1104 +#define EventWindowWillMove 1105 +#define EventWindowWillOrderOffScreen 1106 +#define EventWindowWillOrderOnScreen 1107 +#define EventWindowWillResignMain 1108 +#define EventWindowWillResize 1109 +#define EventWindowWillUnfocus 1110 +#define EventWindowWillUpdate 1111 +#define EventWindowWillUpdateAlpha 1112 +#define EventWindowWillUpdateCollectionBehavior 1113 +#define EventWindowWillUpdateCollectionProperties 1114 +#define EventWindowWillUpdateShadow 1115 +#define EventWindowWillUpdateTitle 1116 +#define EventWindowWillUpdateToolbar 1117 +#define EventWindowWillUpdateVisibility 1118 +#define EventWindowWillUseStandardFrame 1119 +#define EventMenuWillOpen 1120 +#define EventMenuDidOpen 1121 +#define EventMenuDidClose 1122 +#define EventMenuWillSendAction 1123 +#define EventMenuDidSendAction 1124 +#define EventMenuWillHighlightItem 1125 +#define EventMenuDidHighlightItem 1126 +#define EventMenuWillDisplayItem 1127 +#define EventMenuDidDisplayItem 1128 +#define EventMenuWillAddItem 1129 +#define EventMenuDidAddItem 1130 +#define EventMenuWillRemoveItem 1131 +#define EventMenuDidRemoveItem 1132 +#define EventMenuWillBeginTracking 1133 +#define EventMenuDidBeginTracking 1134 +#define EventMenuWillEndTracking 1135 +#define EventMenuDidEndTracking 1136 +#define EventMenuWillUpdate 1137 +#define EventMenuDidUpdate 1138 +#define EventMenuWillPopUp 1139 +#define EventMenuDidPopUp 1140 +#define EventMenuWillSendActionToItem 1141 +#define EventMenuDidSendActionToItem 1142 +#define EventWebViewDidStartProvisionalNavigation 1143 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1144 +#define EventWebViewDidFinishNavigation 1145 +#define EventWebViewDidCommitNavigation 1146 +#define EventWindowFileDraggingEntered 1147 +#define EventWindowFileDraggingPerformed 1148 +#define EventWindowFileDraggingExited 1149 + +#define MAX_EVENTS 1150 + + +#endif \ No newline at end of file diff --git a/v3/pkg/events/events.txt b/v3/pkg/events/events.txt new file mode 100644 index 000000000..3478d3838 --- /dev/null +++ b/v3/pkg/events/events.txt @@ -0,0 +1,210 @@ +common:ApplicationOpenedWithFile +common:ApplicationStarted +common:ApplicationLaunchedWithUrl +common:ThemeChanged +common:WindowClosing +common:WindowDidMove +common:WindowDidResize +common:WindowDPIChanged +common:WindowFilesDropped +common:WindowFocus +common:WindowFullscreen +common:WindowHide +common:WindowLostFocus +common:WindowMaximise +common:WindowMinimise +common:WindowToggleFrameless +common:WindowRestore +common:WindowRuntimeReady +common:WindowShow +common:WindowUnFullscreen +common:WindowUnMaximise +common:WindowUnMinimise +common:WindowZoom +common:WindowZoomIn +common:WindowZoomOut +common:WindowZoomReset +linux:ApplicationStartup +linux:SystemThemeChanged +linux:WindowDeleteEvent +linux:WindowDidMove +linux:WindowDidResize +linux:WindowFocusIn +linux:WindowFocusOut +linux:WindowLoadChanged +mac:ApplicationDidBecomeActive +mac:ApplicationDidChangeBackingProperties +mac:ApplicationDidChangeEffectiveAppearance +mac:ApplicationDidChangeIcon +mac:ApplicationDidChangeOcclusionState +mac:ApplicationDidChangeScreenParameters +mac:ApplicationDidChangeStatusBarFrame +mac:ApplicationDidChangeStatusBarOrientation +mac:ApplicationDidChangeTheme! +mac:ApplicationDidFinishLaunching +mac:ApplicationDidHide +mac:ApplicationDidResignActive +mac:ApplicationDidUnhide +mac:ApplicationDidUpdate +mac:ApplicationShouldHandleReopen! +mac:ApplicationWillBecomeActive +mac:ApplicationWillFinishLaunching +mac:ApplicationWillHide +mac:ApplicationWillResignActive +mac:ApplicationWillTerminate +mac:ApplicationWillUnhide +mac:ApplicationWillUpdate +mac:MenuDidAddItem +mac:MenuDidBeginTracking +mac:MenuDidClose +mac:MenuDidDisplayItem +mac:MenuDidEndTracking +mac:MenuDidHighlightItem +mac:MenuDidOpen +mac:MenuDidPopUp +mac:MenuDidRemoveItem +mac:MenuDidSendAction +mac:MenuDidSendActionToItem +mac:MenuDidUpdate +mac:MenuWillAddItem +mac:MenuWillBeginTracking +mac:MenuWillDisplayItem +mac:MenuWillEndTracking +mac:MenuWillHighlightItem +mac:MenuWillOpen +mac:MenuWillPopUp +mac:MenuWillRemoveItem +mac:MenuWillSendAction +mac:MenuWillSendActionToItem +mac:MenuWillUpdate +mac:WebViewDidCommitNavigation +mac:WebViewDidFinishNavigation +mac:WebViewDidReceiveServerRedirectForProvisionalNavigation +mac:WebViewDidStartProvisionalNavigation +mac:WindowDidBecomeKey +mac:WindowDidBecomeMain +mac:WindowDidBeginSheet +mac:WindowDidChangeAlpha +mac:WindowDidChangeBackingLocation +mac:WindowDidChangeBackingProperties +mac:WindowDidChangeCollectionBehavior +mac:WindowDidChangeEffectiveAppearance +mac:WindowDidChangeOcclusionState +mac:WindowDidChangeOrderingMode +mac:WindowDidChangeScreen +mac:WindowDidChangeScreenParameters +mac:WindowDidChangeScreenProfile +mac:WindowDidChangeScreenSpace +mac:WindowDidChangeScreenSpaceProperties +mac:WindowDidChangeSharingType +mac:WindowDidChangeSpace +mac:WindowDidChangeSpaceOrderingMode +mac:WindowDidChangeTitle +mac:WindowDidChangeToolbar +mac:WindowDidDeminiaturize +mac:WindowDidEndSheet +mac:WindowDidEnterFullScreen +mac:WindowDidEnterVersionBrowser +mac:WindowDidExitFullScreen +mac:WindowDidExitVersionBrowser +mac:WindowDidExpose +mac:WindowDidFocus +mac:WindowDidMiniaturize +mac:WindowDidMove +mac:WindowDidOrderOffScreen +mac:WindowDidOrderOnScreen +mac:WindowDidResignKey +mac:WindowDidResignMain +mac:WindowDidResize +mac:WindowDidUpdate +mac:WindowDidUpdateAlpha +mac:WindowDidUpdateCollectionBehavior +mac:WindowDidUpdateCollectionProperties +mac:WindowDidUpdateShadow +mac:WindowDidUpdateTitle +mac:WindowDidUpdateToolbar +mac:WindowDidZoom! +mac:WindowFileDraggingEntered +mac:WindowFileDraggingExited +mac:WindowFileDraggingPerformed +mac:WindowHide +mac:WindowMaximise! +mac:WindowUnMaximise! +mac:WindowMinimise! +mac:WindowUnMinimise! +mac:WindowShouldClose! +mac:WindowShow +mac:WindowWillBecomeKey +mac:WindowWillBecomeMain +mac:WindowWillBeginSheet +mac:WindowWillChangeOrderingMode +mac:WindowWillClose +mac:WindowWillDeminiaturize +mac:WindowWillEnterFullScreen +mac:WindowWillEnterVersionBrowser +mac:WindowWillExitFullScreen +mac:WindowWillExitVersionBrowser +mac:WindowWillFocus +mac:WindowWillMiniaturize +mac:WindowWillMove +mac:WindowWillOrderOffScreen +mac:WindowWillOrderOnScreen +mac:WindowWillResignMain +mac:WindowWillResize +mac:WindowWillUnfocus +mac:WindowWillUpdate +mac:WindowWillUpdateAlpha +mac:WindowWillUpdateCollectionBehavior +mac:WindowWillUpdateCollectionProperties +mac:WindowWillUpdateShadow +mac:WindowWillUpdateTitle +mac:WindowWillUpdateToolbar +mac:WindowWillUpdateVisibility +mac:WindowWillUseStandardFrame +mac:WindowZoomIn! +mac:WindowZoomOut! +mac:WindowZoomReset! +windows:APMPowerSettingChange +windows:APMPowerStatusChange +windows:APMResumeAutomatic +windows:APMResumeSuspend +windows:APMSuspend +windows:ApplicationStarted +windows:SystemThemeChanged +windows:WebViewNavigationCompleted +windows:WindowActive +windows:WindowBackgroundErase +windows:WindowClickActive +windows:WindowClosing +windows:WindowDidMove +windows:WindowDidResize +windows:WindowDPIChanged +windows:WindowDragDrop +windows:WindowDragEnter +windows:WindowDragLeave +windows:WindowDragOver +windows:WindowEndMove +windows:WindowEndResize +windows:WindowFullscreen +windows:WindowHide +windows:WindowInactive +windows:WindowKeyDown +windows:WindowKeyUp +windows:WindowKillFocus +windows:WindowNonClientHit +windows:WindowNonClientMouseDown +windows:WindowNonClientMouseLeave +windows:WindowNonClientMouseMove +windows:WindowNonClientMouseUp +windows:WindowPaint +windows:WindowRestore +windows:WindowSetFocus +windows:WindowShow +windows:WindowStartMove +windows:WindowStartResize +windows:WindowUnFullscreen +windows:WindowZOrderChanged +windows:WindowMinimise +windows:WindowUnMinimise +windows:WindowMaximise +windows:WindowUnMaximise diff --git a/v3/pkg/events/events_darwin.go b/v3/pkg/events/events_darwin.go new file mode 100644 index 000000000..7c3e3f2d5 --- /dev/null +++ b/v3/pkg/events/events_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin + +package events + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "events_darwin.h" +#include +#include + +bool hasListener[MAX_EVENTS] = {false}; + +void registerListener(unsigned int event) { + hasListener[event] = true; +} + +bool hasListeners(unsigned int event) { + //return hasListener[event]; + return true; +} + +*/ +import "C" diff --git a/v3/pkg/events/events_darwin.h b/v3/pkg/events/events_darwin.h new file mode 100644 index 000000000..31be2554d --- /dev/null +++ b/v3/pkg/events/events_darwin.h @@ -0,0 +1,145 @@ +//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationDidBecomeActive 1057 +#define EventApplicationDidChangeBackingProperties 1058 +#define EventApplicationDidChangeEffectiveAppearance 1059 +#define EventApplicationDidChangeIcon 1060 +#define EventApplicationDidChangeOcclusionState 1061 +#define EventApplicationDidChangeScreenParameters 1062 +#define EventApplicationDidChangeStatusBarFrame 1063 +#define EventApplicationDidChangeStatusBarOrientation 1064 +#define EventApplicationDidChangeTheme 1065 +#define EventApplicationDidFinishLaunching 1066 +#define EventApplicationDidHide 1067 +#define EventApplicationDidResignActive 1068 +#define EventApplicationDidUnhide 1069 +#define EventApplicationDidUpdate 1070 +#define EventApplicationShouldHandleReopen 1071 +#define EventApplicationWillBecomeActive 1072 +#define EventApplicationWillFinishLaunching 1073 +#define EventApplicationWillHide 1074 +#define EventApplicationWillResignActive 1075 +#define EventApplicationWillTerminate 1076 +#define EventApplicationWillUnhide 1077 +#define EventApplicationWillUpdate 1078 +#define EventMenuDidAddItem 1079 +#define EventMenuDidBeginTracking 1080 +#define EventMenuDidClose 1081 +#define EventMenuDidDisplayItem 1082 +#define EventMenuDidEndTracking 1083 +#define EventMenuDidHighlightItem 1084 +#define EventMenuDidOpen 1085 +#define EventMenuDidPopUp 1086 +#define EventMenuDidRemoveItem 1087 +#define EventMenuDidSendAction 1088 +#define EventMenuDidSendActionToItem 1089 +#define EventMenuDidUpdate 1090 +#define EventMenuWillAddItem 1091 +#define EventMenuWillBeginTracking 1092 +#define EventMenuWillDisplayItem 1093 +#define EventMenuWillEndTracking 1094 +#define EventMenuWillHighlightItem 1095 +#define EventMenuWillOpen 1096 +#define EventMenuWillPopUp 1097 +#define EventMenuWillRemoveItem 1098 +#define EventMenuWillSendAction 1099 +#define EventMenuWillSendActionToItem 1100 +#define EventMenuWillUpdate 1101 +#define EventWebViewDidCommitNavigation 1102 +#define EventWebViewDidFinishNavigation 1103 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1104 +#define EventWebViewDidStartProvisionalNavigation 1105 +#define EventWindowDidBecomeKey 1106 +#define EventWindowDidBecomeMain 1107 +#define EventWindowDidBeginSheet 1108 +#define EventWindowDidChangeAlpha 1109 +#define EventWindowDidChangeBackingLocation 1110 +#define EventWindowDidChangeBackingProperties 1111 +#define EventWindowDidChangeCollectionBehavior 1112 +#define EventWindowDidChangeEffectiveAppearance 1113 +#define EventWindowDidChangeOcclusionState 1114 +#define EventWindowDidChangeOrderingMode 1115 +#define EventWindowDidChangeScreen 1116 +#define EventWindowDidChangeScreenParameters 1117 +#define EventWindowDidChangeScreenProfile 1118 +#define EventWindowDidChangeScreenSpace 1119 +#define EventWindowDidChangeScreenSpaceProperties 1120 +#define EventWindowDidChangeSharingType 1121 +#define EventWindowDidChangeSpace 1122 +#define EventWindowDidChangeSpaceOrderingMode 1123 +#define EventWindowDidChangeTitle 1124 +#define EventWindowDidChangeToolbar 1125 +#define EventWindowDidDeminiaturize 1126 +#define EventWindowDidEndSheet 1127 +#define EventWindowDidEnterFullScreen 1128 +#define EventWindowDidEnterVersionBrowser 1129 +#define EventWindowDidExitFullScreen 1130 +#define EventWindowDidExitVersionBrowser 1131 +#define EventWindowDidExpose 1132 +#define EventWindowDidFocus 1133 +#define EventWindowDidMiniaturize 1134 +#define EventWindowDidMove 1135 +#define EventWindowDidOrderOffScreen 1136 +#define EventWindowDidOrderOnScreen 1137 +#define EventWindowDidResignKey 1138 +#define EventWindowDidResignMain 1139 +#define EventWindowDidResize 1140 +#define EventWindowDidUpdate 1141 +#define EventWindowDidUpdateAlpha 1142 +#define EventWindowDidUpdateCollectionBehavior 1143 +#define EventWindowDidUpdateCollectionProperties 1144 +#define EventWindowDidUpdateShadow 1145 +#define EventWindowDidUpdateTitle 1146 +#define EventWindowDidUpdateToolbar 1147 +#define EventWindowDidZoom 1148 +#define EventWindowFileDraggingEntered 1149 +#define EventWindowFileDraggingExited 1150 +#define EventWindowFileDraggingPerformed 1151 +#define EventWindowHide 1152 +#define EventWindowMaximise 1153 +#define EventWindowUnMaximise 1154 +#define EventWindowMinimise 1155 +#define EventWindowUnMinimise 1156 +#define EventWindowShouldClose 1157 +#define EventWindowShow 1158 +#define EventWindowWillBecomeKey 1159 +#define EventWindowWillBecomeMain 1160 +#define EventWindowWillBeginSheet 1161 +#define EventWindowWillChangeOrderingMode 1162 +#define EventWindowWillClose 1163 +#define EventWindowWillDeminiaturize 1164 +#define EventWindowWillEnterFullScreen 1165 +#define EventWindowWillEnterVersionBrowser 1166 +#define EventWindowWillExitFullScreen 1167 +#define EventWindowWillExitVersionBrowser 1168 +#define EventWindowWillFocus 1169 +#define EventWindowWillMiniaturize 1170 +#define EventWindowWillMove 1171 +#define EventWindowWillOrderOffScreen 1172 +#define EventWindowWillOrderOnScreen 1173 +#define EventWindowWillResignMain 1174 +#define EventWindowWillResize 1175 +#define EventWindowWillUnfocus 1176 +#define EventWindowWillUpdate 1177 +#define EventWindowWillUpdateAlpha 1178 +#define EventWindowWillUpdateCollectionBehavior 1179 +#define EventWindowWillUpdateCollectionProperties 1180 +#define EventWindowWillUpdateShadow 1181 +#define EventWindowWillUpdateTitle 1182 +#define EventWindowWillUpdateToolbar 1183 +#define EventWindowWillUpdateVisibility 1184 +#define EventWindowWillUseStandardFrame 1185 +#define EventWindowZoomIn 1186 +#define EventWindowZoomOut 1187 +#define EventWindowZoomReset 1188 + +#define MAX_EVENTS 1189 + + +#endif \ No newline at end of file diff --git a/v3/pkg/events/events_linux.go b/v3/pkg/events/events_linux.go new file mode 100644 index 000000000..2782beb8a --- /dev/null +++ b/v3/pkg/events/events_linux.go @@ -0,0 +1,22 @@ +//go:build linux + +package events + +/* +#include "events_linux.h" +#include +#include + +bool hasListener[MAX_EVENTS] = {false}; + +void registerListener(unsigned int event) { + hasListener[event] = true; +} + +bool hasListeners(unsigned int event) { + //return hasListener[event]; + return true; +} + +*/ +import "C" diff --git a/v3/pkg/events/events_linux.h b/v3/pkg/events/events_linux.h new file mode 100644 index 000000000..9f6ba67b3 --- /dev/null +++ b/v3/pkg/events/events_linux.h @@ -0,0 +1,21 @@ +//go:build linux + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationStartup 1049 +#define EventSystemThemeChanged 1050 +#define EventWindowDeleteEvent 1051 +#define EventWindowDidMove 1052 +#define EventWindowDidResize 1053 +#define EventWindowFocusIn 1054 +#define EventWindowFocusOut 1055 +#define EventWindowLoadChanged 1056 + +#define MAX_EVENTS 1057 + + +#endif \ No newline at end of file diff --git a/v3/pkg/icons/ApplicationDarkMode-256.png b/v3/pkg/icons/ApplicationDarkMode-256.png new file mode 100644 index 000000000..68e5e7666 Binary files /dev/null and b/v3/pkg/icons/ApplicationDarkMode-256.png differ diff --git a/v3/pkg/icons/ApplicationLightMode-256.png b/v3/pkg/icons/ApplicationLightMode-256.png new file mode 100644 index 000000000..fe74e60ea Binary files /dev/null and b/v3/pkg/icons/ApplicationLightMode-256.png differ diff --git a/v3/pkg/icons/DefaultApplicationIcon.png b/v3/pkg/icons/DefaultApplicationIcon.png new file mode 100644 index 000000000..a6129a69f Binary files /dev/null and b/v3/pkg/icons/DefaultApplicationIcon.png differ diff --git a/v3/pkg/icons/DefaultMacTemplateIcon.png b/v3/pkg/icons/DefaultMacTemplateIcon.png new file mode 100644 index 000000000..ee8ad2352 Binary files /dev/null and b/v3/pkg/icons/DefaultMacTemplateIcon.png differ diff --git a/v3/pkg/icons/WailsLogoBlack.png b/v3/pkg/icons/WailsLogoBlack.png new file mode 100644 index 000000000..97269c4bb Binary files /dev/null and b/v3/pkg/icons/WailsLogoBlack.png differ diff --git a/v3/pkg/icons/WailsLogoBlackTransparent.png b/v3/pkg/icons/WailsLogoBlackTransparent.png new file mode 100644 index 000000000..2a767148d Binary files /dev/null and b/v3/pkg/icons/WailsLogoBlackTransparent.png differ diff --git a/v3/pkg/icons/WailsLogoWhite.png b/v3/pkg/icons/WailsLogoWhite.png new file mode 100644 index 000000000..c0e9582cd Binary files /dev/null and b/v3/pkg/icons/WailsLogoWhite.png differ diff --git a/v3/pkg/icons/WailsLogoWhiteTransparent.png b/v3/pkg/icons/WailsLogoWhiteTransparent.png new file mode 100644 index 000000000..e65c582ff Binary files /dev/null and b/v3/pkg/icons/WailsLogoWhiteTransparent.png differ diff --git a/v3/pkg/icons/icon.ico b/v3/pkg/icons/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/pkg/icons/icon.ico differ diff --git a/v3/pkg/icons/icons.go b/v3/pkg/icons/icons.go new file mode 100644 index 000000000..7434ba33c --- /dev/null +++ b/v3/pkg/icons/icons.go @@ -0,0 +1,33 @@ +package icons + +import _ "embed" + +//go:embed DefaultMacTemplateIcon.png +var SystrayMacTemplate []byte + +//go:embed systray-light.png +var SystrayLight []byte + +//go:embed icon.ico +var DefaultWindowsIcon []byte + +//go:embed systray-dark.png +var SystrayDark []byte + +//go:embed ApplicationDarkMode-256.png +var ApplicationDarkMode256 []byte + +//go:embed ApplicationLightMode-256.png +var ApplicationLightMode256 []byte + +//go:embed WailsLogoBlack.png +var WailsLogoBlack []byte + +//go:embed WailsLogoBlackTransparent.png +var WailsLogoBlackTransparent []byte + +//go:embed WailsLogoWhite.png +var WailsLogoWhite []byte + +//go:embed WailsLogoWhiteTransparent.png +var WailsLogoWhiteTransparent []byte diff --git a/v3/pkg/icons/systray-dark.png b/v3/pkg/icons/systray-dark.png new file mode 100644 index 000000000..6bfe5bd64 Binary files /dev/null and b/v3/pkg/icons/systray-dark.png differ diff --git a/v3/pkg/icons/systray-light.png b/v3/pkg/icons/systray-light.png new file mode 100644 index 000000000..a96c3b33c Binary files /dev/null and b/v3/pkg/icons/systray-light.png differ diff --git a/v3/pkg/mac/mac.go b/v3/pkg/mac/mac.go new file mode 100644 index 000000000..73e0258b0 --- /dev/null +++ b/v3/pkg/mac/mac.go @@ -0,0 +1,24 @@ +//go:build darwin + +// Package mac provides a set of functions to interact with the macOS platform. +package mac + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework ServiceManagement + +#import +#import + +// Get the bundle ID +char* getBundleID() { + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + return (char*)[bundleID UTF8String]; +} +*/ +import "C" + +// GetBundleID returns the bundle ID of the application. +func GetBundleID() string { + return C.GoString(C.getBundleID()) +} diff --git a/v3/pkg/services/badge/badge.go b/v3/pkg/services/badge/badge.go new file mode 100644 index 000000000..29b363082 --- /dev/null +++ b/v3/pkg/services/badge/badge.go @@ -0,0 +1,60 @@ +package badge + +import ( + "context" + "image/color" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type platformBadge interface { + // Lifecycle methods + Startup(ctx context.Context, options application.ServiceOptions) error + Shutdown() error + + SetBadge(label string) error + SetCustomBadge(label string, options Options) error + RemoveBadge() error +} + +// Service represents the notifications service +type BadgeService struct { + impl platformBadge +} + +type Options struct { + TextColour color.RGBA + BackgroundColour color.RGBA + FontName string + FontSize int + SmallFontSize int +} + +// ServiceName returns the name of the service. +func (b *BadgeService) ServiceName() string { + return "github.com/wailsapp/wails/v3/services/badge" +} + +// ServiceStartup is called when the service is loaded. +func (b *BadgeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + return b.impl.Startup(ctx, options) +} + +// ServiceShutdown is called when the service is unloaded. +func (b *BadgeService) ServiceShutdown() error { + return b.impl.Shutdown() +} + +// SetBadge sets the badge label on the application icon. +func (b *BadgeService) SetBadge(label string) error { + return b.impl.SetBadge(label) +} + +func (b *BadgeService) SetCustomBadge(label string, options Options) error { + return b.impl.SetCustomBadge(label, options) +} + +// RemoveBadge removes the badge label from the application icon. +func (b *BadgeService) RemoveBadge() error { + return b.impl.RemoveBadge() +} diff --git a/v3/pkg/services/badge/badge_darwin.go b/v3/pkg/services/badge/badge_darwin.go new file mode 100644 index 000000000..cf61c71ca --- /dev/null +++ b/v3/pkg/services/badge/badge_darwin.go @@ -0,0 +1,74 @@ +//go:build darwin + +package badge + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa +#import + +static void setBadge(const char *label) { + NSString *nsLabel = nil; + if (label != NULL) { + nsLabel = [NSString stringWithUTF8String:label]; + } + [[NSApp dockTile] setBadgeLabel:nsLabel]; + [[NSApp dockTile] display]; +} +*/ +import "C" +import ( + "context" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type darwinBadge struct{} + +// Creates a new Badge Service. +func New() *BadgeService { + return &BadgeService{ + impl: &darwinBadge{}, + } +} + +// NewWithOptions creates a new badge service with the given options. +// Currently, options are not available on macOS and are ignored. +// (Windows-specific) +func NewWithOptions(options Options) *BadgeService { + return New() +} + +func (d *darwinBadge) Startup(ctx context.Context, options application.ServiceOptions) error { + return nil +} + +func (d *darwinBadge) Shutdown() error { + return nil +} + +// SetBadge sets the badge label on the application icon. +func (d *darwinBadge) SetBadge(label string) error { + var cLabel *C.char + if label != "" { + cLabel = C.CString(label) + defer C.free(unsafe.Pointer(cLabel)) + } else { + cLabel = C.CString("โ—") // Default badge character + } + C.setBadge(cLabel) + return nil +} + +// SetCustomBadge is not supported on macOS, SetBadge is called instead. +// (Windows-specific) +func (d *darwinBadge) SetCustomBadge(label string, options Options) error { + return d.SetBadge(label) +} + +// RemoveBadge removes the badge label from the application icon. +func (d *darwinBadge) RemoveBadge() error { + C.setBadge(nil) + return nil +} diff --git a/v3/pkg/services/badge/badge_linux.go b/v3/pkg/services/badge/badge_linux.go new file mode 100644 index 000000000..aba02f47d --- /dev/null +++ b/v3/pkg/services/badge/badge_linux.go @@ -0,0 +1,58 @@ +//go:build linux + +package badge + +import ( + "context" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type linuxBadge struct{} + +// New creates a new Badge Service. +// On Linux, this returns a no-op implementation since most desktop environments +// don't have standardized dock badge functionality. +func New() *BadgeService { + return &BadgeService{ + impl: &linuxBadge{}, + } +} + +// NewWithOptions creates a new badge service with the given options. +// On Linux, this returns a no-op implementation since most desktop environments +// don't have standardized dock badge functionality. Options are ignored. +func NewWithOptions(options Options) *BadgeService { + return New() +} + +func (l *linuxBadge) Startup(ctx context.Context, options application.ServiceOptions) error { + // No-op: Linux doesn't have standardized badge support + return nil +} + +func (l *linuxBadge) Shutdown() error { + // No-op: Linux doesn't have standardized badge support + return nil +} + +// SetBadge is a no-op on Linux since most desktop environments don't support +// application dock badges. This method exists for cross-platform compatibility. +func (l *linuxBadge) SetBadge(label string) error { + // No-op: Linux doesn't have standardized badge support + return nil +} + +// SetCustomBadge is a no-op on Linux since most desktop environments don't support +// application dock badges. This method exists for cross-platform compatibility. +func (l *linuxBadge) SetCustomBadge(label string, options Options) error { + // No-op: Linux doesn't have standardized badge support + return nil +} + +// RemoveBadge is a no-op on Linux since most desktop environments don't support +// application dock badges. This method exists for cross-platform compatibility. +func (l *linuxBadge) RemoveBadge() error { + // No-op: Linux doesn't have standardized badge support + return nil +} diff --git a/v3/pkg/services/badge/badge_windows.go b/v3/pkg/services/badge/badge_windows.go new file mode 100644 index 000000000..d7206c87c --- /dev/null +++ b/v3/pkg/services/badge/badge_windows.go @@ -0,0 +1,374 @@ +//go:build windows + +package badge + +import ( + "bytes" + "context" + "image" + "image/color" + "image/png" + "os" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/math/fixed" +) + +type windowsBadge struct { + taskbar *w32.ITaskbarList3 + badgeImg *image.RGBA + badgeSize int + fontManager *FontManager + options Options +} + +var defaultOptions = Options{ + TextColour: color.RGBA{255, 255, 255, 255}, + BackgroundColour: color.RGBA{255, 0, 0, 255}, + FontName: "segoeuib.ttf", + FontSize: 18, + SmallFontSize: 14, +} + +// Creates a new Badge Service. +func New() *BadgeService { + return &BadgeService{ + impl: &windowsBadge{ + options: defaultOptions, + }, + } +} + +// NewWithOptions creates a new badge service with the given options. +func NewWithOptions(options Options) *BadgeService { + return &BadgeService{ + impl: &windowsBadge{ + options: options, + }, + } +} + +func (w *windowsBadge) Startup(ctx context.Context, options application.ServiceOptions) error { + taskbar, err := w32.NewTaskbarList3() + if err != nil { + return err + } + w.taskbar = taskbar + w.fontManager = NewFontManager() + + return nil +} + +func (w *windowsBadge) Shutdown() error { + if w.taskbar != nil { + w.taskbar.Release() + } + + return nil +} + +// SetBadge sets the badge label on the application icon. +func (w *windowsBadge) SetBadge(label string) error { + if w.taskbar == nil { + return nil + } + + app := application.Get() + if app == nil { + return nil + } + + window := app.Window.Current() + if window == nil { + return nil + } + + hwnd, err := window.NativeWindowHandle() + if err != nil { + return err + } + + w.createBadge() + + var hicon w32.HICON + if label == "" { + hicon, err = w.createBadgeIcon() + if err != nil { + return err + } + } else { + hicon, err = w.createBadgeIconWithText(label) + if err != nil { + return err + } + } + defer w32.DestroyIcon(hicon) + + return w.taskbar.SetOverlayIcon(hwnd, hicon, nil) +} + +// SetCustomBadge sets the badge label on the application icon with one-off options. +func (w *windowsBadge) SetCustomBadge(label string, options Options) error { + if w.taskbar == nil { + return nil + } + + app := application.Get() + if app == nil { + return nil + } + + window := app.Window.Current() + if window == nil { + return nil + } + + hwnd, err := window.NativeWindowHandle() + if err != nil { + return err + } + + const badgeSize = 32 + + img := image.NewRGBA(image.Rect(0, 0, badgeSize, badgeSize)) + + backgroundColour := options.BackgroundColour + radius := badgeSize / 2 + centerX, centerY := radius, radius + + for y := 0; y < badgeSize; y++ { + for x := 0; x < badgeSize; x++ { + dx := float64(x - centerX) + dy := float64(y - centerY) + + if dx*dx+dy*dy < float64(radius*radius) { + img.Set(x, y, backgroundColour) + } + } + } + + var hicon w32.HICON + if label == "" { + hicon, err = createBadgeIcon(badgeSize, img, options) + if err != nil { + return err + } + } else { + hicon, err = createBadgeIconWithText(w, label, badgeSize, img, options) + if err != nil { + return err + } + } + defer w32.DestroyIcon(hicon) + + return w.taskbar.SetOverlayIcon(hwnd, hicon, nil) +} + +// RemoveBadge removes the badge label from the application icon. +func (w *windowsBadge) RemoveBadge() error { + if w.taskbar == nil { + return nil + } + + app := application.Get() + if app == nil { + return nil + } + + window := app.Window.Current() + if window == nil { + return nil + } + + hwnd, err := window.NativeWindowHandle() + if err != nil { + return err + } + + return w.taskbar.SetOverlayIcon(hwnd, 0, nil) +} + +// createBadgeIcon creates a badge icon with the specified size and color. +func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) { + radius := w.badgeSize / 2 + centerX, centerY := radius, radius + innerRadius := w.badgeSize / 5 + + for y := 0; y < w.badgeSize; y++ { + for x := 0; x < w.badgeSize; x++ { + dx := float64(x - centerX) + dy := float64(y - centerY) + + if dx*dx+dy*dy < float64(innerRadius*innerRadius) { + w.badgeImg.Set(x, y, w.options.TextColour) + } + } + } + + var buf bytes.Buffer + if err := png.Encode(&buf, w.badgeImg); err != nil { + return 0, err + } + + hicon, err := w32.CreateSmallHIconFromImage(buf.Bytes()) + return hicon, err +} + +func createBadgeIcon(badgeSize int, img *image.RGBA, options Options) (w32.HICON, error) { + radius := badgeSize / 2 + centerX, centerY := radius, radius + innerRadius := badgeSize / 5 + + for y := 0; y < badgeSize; y++ { + for x := 0; x < badgeSize; x++ { + dx := float64(x - centerX) + dy := float64(y - centerY) + + if dx*dx+dy*dy < float64(innerRadius*innerRadius) { + img.Set(x, y, options.TextColour) + } + } + } + + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + return 0, err + } + + hicon, err := w32.CreateSmallHIconFromImage(buf.Bytes()) + return hicon, err +} + +// createBadgeIconWithText creates a badge icon with the specified text. +func (w *windowsBadge) createBadgeIconWithText(label string) (w32.HICON, error) { + fontPath := w.fontManager.FindFontOrDefault(w.options.FontName) + if fontPath == "" { + return w.createBadgeIcon() + } + + fontBytes, err := os.ReadFile(fontPath) + if err != nil { + return w.createBadgeIcon() + } + + ttf, err := opentype.Parse(fontBytes) + if err != nil { + return w.createBadgeIcon() + } + + fontSize := float64(w.options.FontSize) + if len(label) > 1 { + fontSize = float64(w.options.SmallFontSize) + } + + // Get DPI of the current screen + screen := w32.GetDesktopWindow() + dpi := w32.GetDpiForWindow(screen) + + face, err := opentype.NewFace(ttf, &opentype.FaceOptions{ + Size: fontSize, + DPI: float64(dpi), + Hinting: font.HintingFull, + }) + if err != nil { + return w.createBadgeIcon() + } + defer face.Close() + + d := &font.Drawer{ + Dst: w.badgeImg, + Src: image.NewUniform(w.options.TextColour), + Face: face, + } + + textWidth := d.MeasureString(label).Ceil() + d.Dot = fixed.P((w.badgeSize-textWidth)/2, int(float64(w.badgeSize)/2+fontSize/2)) + d.DrawString(label) + + var buf bytes.Buffer + if err := png.Encode(&buf, w.badgeImg); err != nil { + return 0, err + } + + return w32.CreateSmallHIconFromImage(buf.Bytes()) +} + +func createBadgeIconWithText(w *windowsBadge, label string, badgeSize int, img *image.RGBA, options Options) (w32.HICON, error) { + fontPath := w.fontManager.FindFontOrDefault(options.FontName) + if fontPath == "" { + return createBadgeIcon(badgeSize, img, options) + } + + fontBytes, err := os.ReadFile(fontPath) + if err != nil { + return createBadgeIcon(badgeSize, img, options) + } + + ttf, err := opentype.Parse(fontBytes) + if err != nil { + return createBadgeIcon(badgeSize, img, options) + } + + fontSize := float64(options.FontSize) + if len(label) > 1 { + fontSize = float64(options.SmallFontSize) + } + + // Get DPI of the current screen + screen := w32.GetDesktopWindow() + dpi := w32.GetDpiForWindow(screen) + + face, err := opentype.NewFace(ttf, &opentype.FaceOptions{ + Size: fontSize, + DPI: float64(dpi), + Hinting: font.HintingFull, + }) + if err != nil { + return createBadgeIcon(badgeSize, img, options) + } + defer face.Close() + + d := &font.Drawer{ + Dst: img, + Src: image.NewUniform(options.TextColour), + Face: face, + } + + textWidth := d.MeasureString(label).Ceil() + d.Dot = fixed.P((badgeSize-textWidth)/2, int(float64(badgeSize)/2+fontSize/2)) + d.DrawString(label) + + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + return 0, err + } + + return w32.CreateSmallHIconFromImage(buf.Bytes()) +} + +// createBadge creates a circular badge with the specified background color. +func (w *windowsBadge) createBadge() { + w.badgeSize = 32 + + img := image.NewRGBA(image.Rect(0, 0, w.badgeSize, w.badgeSize)) + + backgroundColour := w.options.BackgroundColour + radius := w.badgeSize / 2 + centerX, centerY := radius, radius + + for y := 0; y < w.badgeSize; y++ { + for x := 0; x < w.badgeSize; x++ { + dx := float64(x - centerX) + dy := float64(y - centerY) + + if dx*dx+dy*dy < float64(radius*radius) { + img.Set(x, y, backgroundColour) + } + } + } + + w.badgeImg = img +} diff --git a/v3/pkg/services/badge/font.go b/v3/pkg/services/badge/font.go new file mode 100644 index 000000000..430a9c14d --- /dev/null +++ b/v3/pkg/services/badge/font.go @@ -0,0 +1,153 @@ +//go:build windows + +package badge + +import ( + "errors" + "os" + "path/filepath" + "strings" + "sync" + + "golang.org/x/sys/windows/registry" +) + +// FontManager handles font discovery on Windows with minimal caching +type FontManager struct { + fontCache map[string]string // Maps only requested font filenames to paths + fontDirs []string // Directories to search for fonts + mu sync.RWMutex // Mutex for thread-safe access to the cache + registryPaths []string // Registry paths to search for fonts +} + +// NewFontManager creates a new FontManager instance +func NewFontManager() *FontManager { + return &FontManager{ + fontCache: make(map[string]string), + fontDirs: []string{ + filepath.Join(os.Getenv("windir"), "Fonts"), + filepath.Join(os.Getenv("localappdata"), "Microsoft", "Windows", "Fonts"), + }, + registryPaths: []string{ + `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, + }, + } +} + +// FindFont searches for a font by filename and returns its full path +// Only caches fonts that are found +func (fm *FontManager) FindFont(fontFilename string) (string, error) { + fontKey := strings.ToLower(fontFilename) + + // Check if already in cache + fm.mu.RLock() + if path, exists := fm.fontCache[fontKey]; exists { + fm.mu.RUnlock() + return path, nil + } + fm.mu.RUnlock() + + // If not in cache, search for the font + fontPath, err := fm.searchForFont(fontFilename) + if err != nil { + return "", err + } + + // Add to cache only if found + fm.mu.Lock() + fm.fontCache[fontKey] = fontPath + fm.mu.Unlock() + + return fontPath, nil +} + +// searchForFont looks for a font in all known locations +func (fm *FontManager) searchForFont(fontFilename string) (string, error) { + fontFileLower := strings.ToLower(fontFilename) + + // 1. Direct file check in font directories (fastest approach) + for _, dir := range fm.fontDirs { + fontPath := filepath.Join(dir, fontFilename) + if fileExists(fontPath) { + return fontPath, nil + } + } + + // 2. Search in registry (can find fonts with different paths) + // System fonts (HKEY_LOCAL_MACHINE) + for _, regPath := range fm.registryPaths { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, regPath, registry.QUERY_VALUE) + if err == nil { + defer k.Close() + + // Look for the specific font in registry values + fontPath, found := fm.findFontInRegistry(k, fontFileLower, fm.fontDirs[0]) + if found { + return fontPath, nil + } + } + } + + // 3. User fonts (HKEY_CURRENT_USER) + for _, regPath := range fm.registryPaths { + k, err := registry.OpenKey(registry.CURRENT_USER, regPath, registry.QUERY_VALUE) + if err == nil { + defer k.Close() + + // Look for the specific font in registry values + fontPath, found := fm.findFontInRegistry(k, fontFileLower, fm.fontDirs[1]) + if found { + return fontPath, nil + } + } + } + + return "", errors.New("font not found: " + fontFilename) +} + +// findFontInRegistry searches for a specific font in a registry key +func (fm *FontManager) findFontInRegistry(k registry.Key, fontFileLower string, defaultDir string) (string, bool) { + valueNames, err := k.ReadValueNames(0) + if err != nil { + return "", false + } + + for _, name := range valueNames { + value, _, err := k.GetStringValue(name) + if err != nil { + continue + } + + // Check if this registry entry corresponds to our font + valueLower := strings.ToLower(value) + if strings.HasSuffix(valueLower, fontFileLower) { + // If it's a relative path, assume it's in the default font directory + if !strings.Contains(value, "\\") { + value = filepath.Join(defaultDir, value) + } + + if fileExists(value) { + return value, true + } + } + } + + return "", false +} + +func (fm *FontManager) FindFontOrDefault(name string) string { + fontsToFind := []string{name, "segoeuib.ttf", "arialbd.ttf"} + for _, font := range fontsToFind { + path, err := fm.FindFont(font) + if err == nil { + return path + } + } + return "" +} + +// Helper functions +func fileExists(path string) bool { + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} diff --git a/v3/pkg/services/fileserver/fileserver.go b/v3/pkg/services/fileserver/fileserver.go new file mode 100644 index 000000000..de06f5c1a --- /dev/null +++ b/v3/pkg/services/fileserver/fileserver.go @@ -0,0 +1,54 @@ +package fileserver + +import ( + "net/http" + "sync/atomic" +) + +type Config struct { + // RootPath specifies the filesystem path from which requests are to be served. + RootPath string +} + +type FileserverService struct { + fs atomic.Pointer[http.Handler] +} + +// New initialises an unconfigured fileserver. See [Configure] for details. +func New() *FileserverService { + return NewWithConfig(nil) +} + +// New initialises and optionally configures a fileserver. See [Service.Configure] for details. +func NewWithConfig(config *Config) *FileserverService { + result := &FileserverService{} + result.Configure(config) + return result +} + +// ServiceName returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (s *FileserverService) ServiceName() string { + return "github.com/wailsapp/wails/v3/services/fileserver" +} + +// Configure reconfigures the fileserver. +// If config is nil, then every request will receive a 503 Service Unavailable response. +// +//wails:ignore +func (s *FileserverService) Configure(config *Config) { + if config == nil { + s.fs.Store(&dummyHandler) + } else { + var fs http.Handler = http.FileServer(http.Dir(config.RootPath)) + s.fs.Store(&fs) + } +} + +func (s *FileserverService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + (*s.fs.Load()).ServeHTTP(w, r) +} + +var dummyHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Fileserver service has not been configured yet", http.StatusServiceUnavailable) +}) diff --git a/v3/pkg/services/kvstore/kvstore.go b/v3/pkg/services/kvstore/kvstore.go new file mode 100644 index 000000000..899dd86ec --- /dev/null +++ b/v3/pkg/services/kvstore/kvstore.go @@ -0,0 +1,228 @@ +package kvstore + +import ( + "context" + "encoding/json" + "os" + "sync" + + "github.com/pkg/errors" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Config struct { + // Filename specifies the path of the on-disk file associated to the key-value store. + Filename string + + // AutoSave specifies whether the store + // must be written to disk automatically after every modification. + // When AutoSave is false, stores are only saved to disk upon shutdown + // or when the [Service.Save] method is called manually. + AutoSave bool +} + +type KVStoreService struct { + lock sync.RWMutex + + config *Config + + data map[string]any + unsaved bool +} + +// New initialises an in-memory key-value store. See [NewWithConfig] for details. +func New() *KVStoreService { + return NewWithConfig(nil) +} + +// NewWithConfig initialises a key-value store with the given configuration: +// - if config is nil, the new store is in-memory, i.e. not associated with a file; +// - if config is non-nil, the associated file is not loaded until [Service.Load] is called. +// +// If the store is registered with the application as a service, +// [Service.Load] will be called automatically at startup. +func NewWithConfig(config *Config) *KVStoreService { + result := &KVStoreService{data: make(map[string]any)} + result.Configure(config) + return result +} + +// ServiceName returns the name of the plugin. +func (kvs *KVStoreService) ServiceName() string { + return "github.com/wailsapp/wails/v3/plugins/kvstore" +} + +// ServiceStartup loads the store from disk if it is associated with a file. +// It returns a non-nil error in case of failure. +func (kvs *KVStoreService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + return errors.Wrap(kvs.Load(), "error loading store") +} + +// ServiceShutdown saves the store to disk if it is associated with a file. +// It returns a non-nil error in case of failure. +func (kvs *KVStoreService) ServiceShutdown() error { + return errors.Wrap(kvs.Save(), "error saving store") +} + +// Configure changes the store's configuration. +// The contents of the store at call time are preserved and marked unsaved. +// Consumers will need to call [Service.Load] manually after Configure +// in order to load a new file. +// +// If the store is unsaved upon calling Configure, no attempt is made at saving it. +// Consumers will need to call [Service.Save] manually beforehand. +// +// See [NewWithConfig] for details on configuration. +// +//wails:ignore +func (kvs *KVStoreService) Configure(config *Config) { + if config != nil { + // Clone to prevent changes from the outside. + clone := new(Config) + *clone = *config + config = clone + } + + kvs.lock.Lock() + defer kvs.lock.Unlock() + + kvs.config = config + kvs.unsaved = true +} + +// Load loads the store from disk. +// If the store is in-memory, i.e. not associated with a file, Load has no effect. +// If the operation fails, a non-nil error is returned +// and the store's content and state at call time are preserved. +func (kvs *KVStoreService) Load() error { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + if kvs.config == nil { + return nil + } + + bytes, err := os.ReadFile(kvs.config.Filename) + if err != nil { + if os.IsNotExist(err) { + return nil + } else { + return err + } + } + + // Init new map because [json.Unmarshal] does not clear the previous one. + data := make(map[string]any) + + if len(bytes) > 0 { + if err := json.Unmarshal(bytes, &data); err != nil { + return err + } + } + + kvs.data = data + kvs.unsaved = false + return nil +} + +// Save saves the store to disk. +// If the store is in-memory, i.e. not associated with a file, Save has no effect. +func (kvs *KVStoreService) Save() error { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + if kvs.config == nil { + return nil + } + + bytes, err := json.Marshal(kvs.data) + if err != nil { + return err + } + + err = os.WriteFile(kvs.config.Filename, bytes, 0644) + if err != nil { + return err + } + + kvs.unsaved = false + return nil +} + +// Get returns the value for the given key. If key is empty, the entire store is returned. +func (kvs *KVStoreService) Get(key string) any { + kvs.lock.RLock() + defer kvs.lock.RUnlock() + + if key == "" { + return kvs.data + } + + return kvs.data[key] +} + +// Set sets the value for the given key. If AutoSave is true, the store is saved to disk. +func (kvs *KVStoreService) Set(key string, value any) error { + var autosave bool + func() { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + kvs.data[key] = value + kvs.unsaved = true + + if kvs.config != nil { + autosave = kvs.config.AutoSave + } + }() + + if autosave { + return kvs.Save() + } else { + return nil + } +} + +// Delete deletes the given key from the store. If AutoSave is true, the store is saved to disk. +func (kvs *KVStoreService) Delete(key string) error { + var autosave bool + func() { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + delete(kvs.data, key) + kvs.unsaved = true + + if kvs.config != nil { + autosave = kvs.config.AutoSave + } + }() + + if autosave { + return kvs.Save() + } else { + return nil + } +} + +// Clear deletes all keys from the store. If AutoSave is true, the store is saved to disk. +func (kvs *KVStoreService) Clear() error { + var autosave bool + func() { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + kvs.data = make(map[string]any) + kvs.unsaved = true + + if kvs.config != nil { + autosave = kvs.config.AutoSave + } + }() + + if autosave { + return kvs.Save() + } else { + return nil + } +} diff --git a/v3/pkg/services/log/log.go b/v3/pkg/services/log/log.go new file mode 100644 index 000000000..3c8c6300c --- /dev/null +++ b/v3/pkg/services/log/log.go @@ -0,0 +1,195 @@ +package log + +import ( + "context" + _ "embed" + "log/slog" + "sync/atomic" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// A Level is the importance or severity of a log event. +// The higher the level, the more important or severe the event. +// +// Values are arbitrary, but there are four predefined ones. +type Level = int + +const ( + Debug = Level(slog.LevelDebug) + Info = Level(slog.LevelInfo) + Warning = Level(slog.LevelWarn) + Error = Level(slog.LevelError) +) + +type Config struct { + // Logger is the logger to use. If not set, a default logger will be used. + Logger *slog.Logger + + // LogLevel defines the log level of the logger. + LogLevel slog.Level +} + +//wails:inject export { +//wails:inject DebugContext as Debug, +//wails:inject InfoContext as Info, +//wails:inject WarningContext as Warning, +//wails:inject ErrorContext as Error, +//wails:inject }; +type LogService struct { + config atomic.Pointer[Config] + level slog.LevelVar +} + +// New initialises a logging service with the default configuration. +func New() *LogService { + return NewWithConfig(nil) +} + +// NewWithConfig initialises a logging service with a custom configuration. +func NewWithConfig(config *Config) *LogService { + result := &LogService{} + result.Configure(config) + return result +} + +// ServiceName returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (l *LogService) ServiceName() string { + return "github.com/wailsapp/wails/v3/plugins/log" +} + +// Configure reconfigures the logger dynamically. +// If config is nil, it falls back to the default configuration. +// +//wails:ignore +func (l *LogService) Configure(config *Config) { + if config == nil { + config = &Config{} + } else { + // Clone to prevent changes from the outside. + clone := new(Config) + *clone = *config + config = clone + } + + l.level.Set(slog.Level(config.LogLevel)) + + if config.Logger == nil { + config.Logger = application.DefaultLogger(&l.level) + } + + l.config.Store(config) +} + +// Level returns the currently configured log level, +// that is either the one configured initially +// or the last value passed to [Service.SetLogLevel]. +// +// Through this method, [Service] implements the [slog.Leveler] interface. +// The intended use case is to propagate +// the service's dynamic level setting to custom loggers. +// For example: +// +// logService := log.New() +// customLogger := slog.New(slog.NewTextHandler( +// customWriter, +// &slog.HandlerOptions{ +// Level: logService, +// }, +// )) +// logService.Configure(&log.Config{ +// Logger: customLogger +// }) +// +// By doing so, setting updates made through [Service.SetLogLevel] +// will propagate dynamically to the custom logger. +// +//wails:ignore +func (l *LogService) Level() slog.Level { + return l.level.Level() +} + +// LogLevel returns the currently configured log level, +// that is either the one configured initially +// or the last value passed to [Service.SetLogLevel]. +func (l *LogService) LogLevel() Level { + return Level(l.Level()) +} + +// SetLogLevel changes the current log level. +func (l *LogService) SetLogLevel(level Level) { + l.level.Set(slog.Level(level)) +} + +// Log emits a log record with the current time and the given level and message. +// The Record's attributes consist of the Logger's attributes followed by +// the attributes specified by args. +// +// The attribute arguments are processed as follows: +// - If an argument is a string and this is not the last argument, +// the following argument is treated as the value and the two are combined +// into an attribute. +// - Otherwise, the argument is treated as a value with key "!BADKEY". +// +// Log feeds the binding call context into the configured logger, +// so custom handlers may access context values, e.g. the current window. +func (l *LogService) Log(ctx context.Context, level Level, message string, args ...any) { + l.config.Load().Logger.Log(ctx, slog.Level(level), message, args...) +} + +// Debug logs at level [Debug]. +// +//wails:ignore +func (l *LogService) Debug(message string, args ...any) { + l.DebugContext(context.Background(), message, args...) +} + +// Info logs at level [Info]. +// +//wails:ignore +func (l *LogService) Info(message string, args ...any) { + l.InfoContext(context.Background(), message, args...) +} + +// Warning logs at level [Warning]. +// +//wails:ignore +func (l *LogService) Warning(message string, args ...any) { + l.WarningContext(context.Background(), message, args...) +} + +// Error logs at level [Error]. +// +//wails:ignore +func (l *LogService) Error(message string, args ...any) { + l.ErrorContext(context.Background(), message, args...) +} + +// DebugContext logs at level [Debug]. +// +//wails:internal +func (l *LogService) DebugContext(ctx context.Context, message string, args ...any) { + l.Log(ctx, Debug, message, args...) +} + +// InfoContext logs at level [Info]. +// +//wails:internal +func (l *LogService) InfoContext(ctx context.Context, message string, args ...any) { + l.Log(ctx, Info, message, args...) +} + +// WarningContext logs at level [Warn]. +// +//wails:internal +func (l *LogService) WarningContext(ctx context.Context, message string, args ...any) { + l.Log(ctx, Warning, message, args...) +} + +// ErrorContext logs at level [Error]. +// +//wails:internal +func (l *LogService) ErrorContext(ctx context.Context, message string, args ...any) { + l.Log(ctx, Error, message, args...) +} diff --git a/v3/pkg/services/notifications/notifications.go b/v3/pkg/services/notifications/notifications.go new file mode 100644 index 000000000..3382d220b --- /dev/null +++ b/v3/pkg/services/notifications/notifications.go @@ -0,0 +1,216 @@ +// Package notifications provides cross-platform notification capabilities for desktop applications. +// It supports macOS, Windows, and Linux with a consistent API while handling platform-specific +// differences internally. Key features include: +// - Basic notifications with title, subtitle, and body +// - Interactive notifications with buttons and actions +// - Notification categories for reusing configurations +// - User feedback handling with a unified callback system +// +// Platform-specific notes: +// - macOS: Requires a properly bundled and signed application +// - Windows: Uses Windows Toast notifications +// - Linux: Uses D-Bus and does not support text inputs +package notifications + +import ( + "context" + "fmt" + "sync" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type platformNotifier interface { + // Lifecycle methods + Startup(ctx context.Context, options application.ServiceOptions) error + Shutdown() error + + // Core notification methods + RequestNotificationAuthorization() (bool, error) + CheckNotificationAuthorization() (bool, error) + SendNotification(options NotificationOptions) error + SendNotificationWithActions(options NotificationOptions) error + + // Category management + RegisterNotificationCategory(category NotificationCategory) error + RemoveNotificationCategory(categoryID string) error + + // Notification management + RemoveAllPendingNotifications() error + RemovePendingNotification(identifier string) error + RemoveAllDeliveredNotifications() error + RemoveDeliveredNotification(identifier string) error + RemoveNotification(identifier string) error +} + +// Service represents the notifications service +type NotificationService struct { + impl platformNotifier + + // notificationResponseCallback is called when a notification result is received. + // Only one callback can be assigned at a time. + notificationResultCallback func(result NotificationResult) + + callbackLock sync.RWMutex +} + +var ( + notificationServiceOnce sync.Once + NotificationService_ *NotificationService + notificationServiceLock sync.RWMutex +) + +// NotificationAction represents an action button for a notification. +type NotificationAction struct { + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Destructive bool `json:"destructive,omitempty"` // (macOS-specific) +} + +// NotificationCategory groups actions for notifications. +type NotificationCategory struct { + ID string `json:"id,omitempty"` + Actions []NotificationAction `json:"actions,omitempty"` + HasReplyField bool `json:"hasReplyField,omitempty"` + ReplyPlaceholder string `json:"replyPlaceholder,omitempty"` + ReplyButtonTitle string `json:"replyButtonTitle,omitempty"` +} + +// NotificationOptions contains configuration for a notification +type NotificationOptions struct { + ID string `json:"id"` + Title string `json:"title"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + CategoryID string `json:"categoryId,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` +} + +const DefaultActionIdentifier = "DEFAULT_ACTION" + +// NotificationResponse represents the response sent by interacting with a notification. +type NotificationResponse struct { + ID string `json:"id,omitempty"` + ActionIdentifier string `json:"actionIdentifier,omitempty"` + CategoryID string `json:"categoryIdentifier,omitempty"` + Title string `json:"title,omitempty"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + UserText string `json:"userText,omitempty"` + UserInfo map[string]interface{} `json:"userInfo,omitempty"` +} + +// NotificationResult represents the result of a notification response, +// returning the response or any errors that occurred. +type NotificationResult struct { + Response NotificationResponse + Error error +} + +// ServiceName returns the name of the service. +func (ns *NotificationService) ServiceName() string { + return "github.com/wailsapp/wails/v3/services/notifications" +} + +// OnNotificationResponse registers a callback function that will be called when +// a notification response is received from the user. +// +//wails:ignore +func (ns *NotificationService) OnNotificationResponse(callback func(result NotificationResult)) { + ns.callbackLock.Lock() + defer ns.callbackLock.Unlock() + + ns.notificationResultCallback = callback +} + +// handleNotificationResponse is an internal method to handle notification responses +// and invoke the registered callback if one exists. +func (ns *NotificationService) handleNotificationResult(result NotificationResult) { + ns.callbackLock.RLock() + callback := ns.notificationResultCallback + ns.callbackLock.RUnlock() + + if callback != nil { + callback(result) + } +} + +// ServiceStartup is called when the service is loaded. +func (ns *NotificationService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + return ns.impl.Startup(ctx, options) +} + +// ServiceShutdown is called when the service is unloaded. +func (ns *NotificationService) ServiceShutdown() error { + return ns.impl.Shutdown() +} + +// Public methods that delegate to the implementation. +func (ns *NotificationService) RequestNotificationAuthorization() (bool, error) { + return ns.impl.RequestNotificationAuthorization() +} + +func (ns *NotificationService) CheckNotificationAuthorization() (bool, error) { + return ns.impl.CheckNotificationAuthorization() +} + +func (ns *NotificationService) SendNotification(options NotificationOptions) error { + if err := validateNotificationOptions(options); err != nil { + return err + } + return ns.impl.SendNotification(options) +} + +func (ns *NotificationService) SendNotificationWithActions(options NotificationOptions) error { + if err := validateNotificationOptions(options); err != nil { + return err + } + return ns.impl.SendNotificationWithActions(options) +} + +func (ns *NotificationService) RegisterNotificationCategory(category NotificationCategory) error { + return ns.impl.RegisterNotificationCategory(category) +} + +func (ns *NotificationService) RemoveNotificationCategory(categoryID string) error { + return ns.impl.RemoveNotificationCategory(categoryID) +} + +func (ns *NotificationService) RemoveAllPendingNotifications() error { + return ns.impl.RemoveAllPendingNotifications() +} + +func (ns *NotificationService) RemovePendingNotification(identifier string) error { + return ns.impl.RemovePendingNotification(identifier) +} + +func (ns *NotificationService) RemoveAllDeliveredNotifications() error { + return ns.impl.RemoveAllDeliveredNotifications() +} + +func (ns *NotificationService) RemoveDeliveredNotification(identifier string) error { + return ns.impl.RemoveDeliveredNotification(identifier) +} + +func (ns *NotificationService) RemoveNotification(identifier string) error { + return ns.impl.RemoveNotification(identifier) +} + +func getNotificationService() *NotificationService { + notificationServiceLock.RLock() + defer notificationServiceLock.RUnlock() + return NotificationService_ +} + +// validateNotificationOptions validates an ID and Title are provided for notifications. +func validateNotificationOptions(options NotificationOptions) error { + if options.ID == "" { + return fmt.Errorf("notification ID cannot be empty") + } + + if options.Title == "" { + return fmt.Errorf("notification title cannot be empty") + } + + return nil +} diff --git a/v3/pkg/services/notifications/notifications_darwin.go b/v3/pkg/services/notifications/notifications_darwin.go new file mode 100644 index 000000000..263e780d4 --- /dev/null +++ b/v3/pkg/services/notifications/notifications_darwin.go @@ -0,0 +1,426 @@ +//go:build darwin + +package notifications + +/* +#cgo CFLAGS:-x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 +#cgo LDFLAGS: -framework UserNotifications +#endif + +#import "./notifications_darwin.h" +*/ +import "C" +import ( + "context" + "encoding/json" + "fmt" + "sync" + "time" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +type darwinNotifier struct { + channels map[int]chan notificationChannel + channelsLock sync.Mutex + nextChannelID int +} + +type notificationChannel struct { + Success bool + Error error +} + +type ChannelHandler interface { + GetChannel(id int) (chan notificationChannel, bool) +} + +const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier" + +// Creates a new Notifications Service. +// Your app must be packaged and signed for this feature to work. +func New() *NotificationService { + notificationServiceOnce.Do(func() { + impl := &darwinNotifier{ + channels: make(map[int]chan notificationChannel), + nextChannelID: 0, + } + + NotificationService_ = &NotificationService{ + impl: impl, + } + }) + + return NotificationService_ +} + +func (dn *darwinNotifier) Startup(ctx context.Context, options application.ServiceOptions) error { + if !isNotificationAvailable() { + return fmt.Errorf("notifications are not available on this system") + } + if !checkBundleIdentifier() { + return fmt.Errorf("notifications require a valid bundle identifier") + } + if !bool(C.ensureDelegateInitialized()) { + return fmt.Errorf("failed to initialize notification center delegate") + } + return nil +} + +func (dn *darwinNotifier) Shutdown() error { + return nil +} + +// isNotificationAvailable checks if notifications are available on the system. +func isNotificationAvailable() bool { + return bool(C.isNotificationAvailable()) +} + +func checkBundleIdentifier() bool { + return bool(C.checkBundleIdentifier()) +} + +// RequestNotificationAuthorization requests permission for notifications. +// Default timeout is 3 minutes +func (dn *darwinNotifier) RequestNotificationAuthorization() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) + defer cancel() + + id, resultCh := dn.registerChannel() + + C.requestNotificationAuthorization(C.int(id)) + + select { + case result := <-resultCh: + return result.Success, result.Error + case <-ctx.Done(): + dn.cleanupChannel(id) + return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err()) + } +} + +// CheckNotificationAuthorization checks current notification permission status. +func (dn *darwinNotifier) CheckNotificationAuthorization() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + id, resultCh := dn.registerChannel() + + C.checkNotificationAuthorization(C.int(id)) + + select { + case result := <-resultCh: + return result.Success, result.Error + case <-ctx.Done(): + dn.cleanupChannel(id) + return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err()) + } +} + +// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body. +func (dn *darwinNotifier) SendNotification(options NotificationOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cIdentifier := C.CString(options.ID) + cTitle := C.CString(options.Title) + cSubtitle := C.CString(options.Subtitle) + cBody := C.CString(options.Body) + defer C.free(unsafe.Pointer(cIdentifier)) + defer C.free(unsafe.Pointer(cTitle)) + defer C.free(unsafe.Pointer(cSubtitle)) + defer C.free(unsafe.Pointer(cBody)) + + var cDataJSON *C.char + if options.Data != nil { + jsonData, err := json.Marshal(options.Data) + if err != nil { + return fmt.Errorf("failed to marshal notification data: %w", err) + } + cDataJSON = C.CString(string(jsonData)) + defer C.free(unsafe.Pointer(cDataJSON)) + } + + id, resultCh := dn.registerChannel() + C.sendNotification(C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON) + + select { + case result := <-resultCh: + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("sending notification failed") + } + return nil + case <-ctx.Done(): + dn.cleanupChannel(id) + return fmt.Errorf("sending notification timed out: %w", ctx.Err()) + } +} + +// SendNotificationWithActions sends a notification with additional actions and inputs. +// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category. +// If a NotificationCategory is not registered a basic notification will be sent. +func (dn *darwinNotifier) SendNotificationWithActions(options NotificationOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cIdentifier := C.CString(options.ID) + cTitle := C.CString(options.Title) + cSubtitle := C.CString(options.Subtitle) + cBody := C.CString(options.Body) + cCategoryID := C.CString(options.CategoryID) + defer C.free(unsafe.Pointer(cIdentifier)) + defer C.free(unsafe.Pointer(cTitle)) + defer C.free(unsafe.Pointer(cSubtitle)) + defer C.free(unsafe.Pointer(cBody)) + defer C.free(unsafe.Pointer(cCategoryID)) + + var cDataJSON *C.char + if options.Data != nil { + jsonData, err := json.Marshal(options.Data) + if err != nil { + return fmt.Errorf("failed to marshal notification data: %w", err) + } + cDataJSON = C.CString(string(jsonData)) + defer C.free(unsafe.Pointer(cDataJSON)) + } + + id, resultCh := dn.registerChannel() + C.sendNotificationWithActions(C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON) + + select { + case result := <-resultCh: + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("sending notification failed") + } + return nil + case <-ctx.Done(): + dn.cleanupChannel(id) + return fmt.Errorf("sending notification timed out: %w", ctx.Err()) + } +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +// Registering a category with the same name as a previously registered NotificationCategory will override it. +func (dn *darwinNotifier) RegisterNotificationCategory(category NotificationCategory) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cCategoryID := C.CString(category.ID) + defer C.free(unsafe.Pointer(cCategoryID)) + + actionsJSON, err := json.Marshal(category.Actions) + if err != nil { + return fmt.Errorf("failed to marshal notification category: %w", err) + } + cActionsJSON := C.CString(string(actionsJSON)) + defer C.free(unsafe.Pointer(cActionsJSON)) + + var cReplyPlaceholder, cReplyButtonTitle *C.char + if category.HasReplyField { + cReplyPlaceholder = C.CString(category.ReplyPlaceholder) + cReplyButtonTitle = C.CString(category.ReplyButtonTitle) + defer C.free(unsafe.Pointer(cReplyPlaceholder)) + defer C.free(unsafe.Pointer(cReplyButtonTitle)) + } + + id, resultCh := dn.registerChannel() + C.registerNotificationCategory(C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField), + cReplyPlaceholder, cReplyButtonTitle) + + select { + case result := <-resultCh: + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("category registration failed") + } + return nil + case <-ctx.Done(): + dn.cleanupChannel(id) + return fmt.Errorf("category registration timed out: %w", ctx.Err()) + } +} + +// RemoveNotificationCategory remove a previously registered NotificationCategory. +func (dn *darwinNotifier) RemoveNotificationCategory(categoryId string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cCategoryID := C.CString(categoryId) + defer C.free(unsafe.Pointer(cCategoryID)) + + id, resultCh := dn.registerChannel() + C.removeNotificationCategory(C.int(id), cCategoryID) + + select { + case result := <-resultCh: + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("category removal failed") + } + return nil + case <-ctx.Done(): + dn.cleanupChannel(id) + return fmt.Errorf("category removal timed out: %w", ctx.Err()) + } +} + +// RemoveAllPendingNotifications removes all pending notifications. +func (dn *darwinNotifier) RemoveAllPendingNotifications() error { + C.removeAllPendingNotifications() + return nil +} + +// RemovePendingNotification removes a pending notification matching the unique identifier. +func (dn *darwinNotifier) RemovePendingNotification(identifier string) error { + cIdentifier := C.CString(identifier) + defer C.free(unsafe.Pointer(cIdentifier)) + C.removePendingNotification(cIdentifier) + return nil +} + +// RemoveAllDeliveredNotifications removes all delivered notifications. +func (dn *darwinNotifier) RemoveAllDeliveredNotifications() error { + C.removeAllDeliveredNotifications() + return nil +} + +// RemoveDeliveredNotification removes a delivered notification matching the unique identifier. +func (dn *darwinNotifier) RemoveDeliveredNotification(identifier string) error { + cIdentifier := C.CString(identifier) + defer C.free(unsafe.Pointer(cIdentifier)) + C.removeDeliveredNotification(cIdentifier) + return nil +} + +// RemoveNotification is a macOS stub that always returns nil. +// Use one of the following instead: +// RemoveAllPendingNotifications +// RemovePendingNotification +// RemoveAllDeliveredNotifications +// RemoveDeliveredNotification +// (Linux-specific) +func (dn *darwinNotifier) RemoveNotification(identifier string) error { + return nil +} + +//export captureResult +func captureResult(channelID C.int, success C.bool, errorMsg *C.char) { + ns := getNotificationService() + if ns == nil { + return + } + + handler, ok := ns.impl.(ChannelHandler) + if !ok { + return + } + + resultCh, exists := handler.GetChannel(int(channelID)) + if !exists { + return + } + + var err error + if errorMsg != nil { + err = fmt.Errorf("%s", C.GoString(errorMsg)) + } + + resultCh <- notificationChannel{ + Success: bool(success), + Error: err, + } + + close(resultCh) +} + +//export didReceiveNotificationResponse +func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) { + result := NotificationResult{} + + if err != nil { + errMsg := C.GoString(err) + result.Error = fmt.Errorf("notification response error: %s", errMsg) + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } + return + } + + if jsonPayload == nil { + result.Error = fmt.Errorf("received nil JSON payload in notification response") + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } + return + } + + payload := C.GoString(jsonPayload) + + var response NotificationResponse + if err := json.Unmarshal([]byte(payload), &response); err != nil { + result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err) + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } + return + } + + if response.ActionIdentifier == AppleDefaultActionIdentifier { + response.ActionIdentifier = DefaultActionIdentifier + } + + result.Response = response + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } +} + +// Helper methods + +func (dn *darwinNotifier) registerChannel() (int, chan notificationChannel) { + dn.channelsLock.Lock() + defer dn.channelsLock.Unlock() + + id := dn.nextChannelID + dn.nextChannelID++ + + resultCh := make(chan notificationChannel, 1) + + dn.channels[id] = resultCh + return id, resultCh +} + +func (dn *darwinNotifier) GetChannel(id int) (chan notificationChannel, bool) { + dn.channelsLock.Lock() + defer dn.channelsLock.Unlock() + + ch, exists := dn.channels[id] + if exists { + delete(dn.channels, id) + } + return ch, exists +} + +func (dn *darwinNotifier) cleanupChannel(id int) { + dn.channelsLock.Lock() + defer dn.channelsLock.Unlock() + + if ch, exists := dn.channels[id]; exists { + delete(dn.channels, id) + close(ch) + } +} diff --git a/v3/pkg/services/notifications/notifications_darwin.h b/v3/pkg/services/notifications/notifications_darwin.h new file mode 100644 index 000000000..71e167656 --- /dev/null +++ b/v3/pkg/services/notifications/notifications_darwin.h @@ -0,0 +1,22 @@ +//go:build darwin + +#ifndef NOTIFICATIONS_DARWIN_H +#define NOTIFICATIONS_DARWIN_H + +#import + +bool isNotificationAvailable(void); +bool checkBundleIdentifier(void); +bool ensureDelegateInitialized(void); +void requestNotificationAuthorization(int channelID); +void checkNotificationAuthorization(int channelID); +void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json); +void sendNotificationWithActions(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json); +void registerNotificationCategory(int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle); +void removeNotificationCategory(int channelID, const char *categoryId); +void removeAllPendingNotifications(void); +void removePendingNotification(const char *identifier); +void removeAllDeliveredNotifications(void); +void removeDeliveredNotification(const char *identifier); + +#endif /* NOTIFICATIONS_DARWIN_H */ \ No newline at end of file diff --git a/v3/pkg/services/notifications/notifications_darwin.m b/v3/pkg/services/notifications/notifications_darwin.m new file mode 100644 index 000000000..373197cb0 --- /dev/null +++ b/v3/pkg/services/notifications/notifications_darwin.m @@ -0,0 +1,377 @@ +#import "notifications_darwin.h" +#include +#import + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 +#import +#endif + +bool isNotificationAvailable(void) { + if (@available(macOS 11.0, *)) { + return YES; + } else { + return NO; + } +} + +bool checkBundleIdentifier(void) { + NSBundle *main = [NSBundle mainBundle]; + if (main.bundleIdentifier == nil) { + return NO; + } + return YES; +} + +extern void captureResult(int channelID, bool success, const char* error); +extern void didReceiveNotificationResponse(const char *jsonPayload, const char* error); + +@interface NotificationsDelegate : NSObject +@end + +@implementation NotificationsDelegate + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { + UNNotificationPresentationOptions options = 0; + + if (@available(macOS 11.0, *)) { + // These options are only available in macOS 11.0+ + options = UNNotificationPresentationOptionList | + UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionSound; + } + + completionHandler(options); +} + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { + + NSMutableDictionary *payload = [NSMutableDictionary dictionary]; + + [payload setObject:response.notification.request.identifier forKey:@"id"]; + [payload setObject:response.actionIdentifier forKey:@"actionIdentifier"]; + [payload setObject:response.notification.request.content.title ?: @"" forKey:@"title"]; + [payload setObject:response.notification.request.content.body ?: @"" forKey:@"body"]; + + if (response.notification.request.content.categoryIdentifier) { + [payload setObject:response.notification.request.content.categoryIdentifier forKey:@"categoryIdentifier"]; + } + + if (response.notification.request.content.subtitle) { + [payload setObject:response.notification.request.content.subtitle forKey:@"subtitle"]; + } + + if (response.notification.request.content.userInfo) { + [payload setObject:response.notification.request.content.userInfo forKey:@"userInfo"]; + } + + if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) { + UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response; + [payload setObject:textResponse.userText forKey:@"userText"]; + } + + NSError *error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error]; + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + didReceiveNotificationResponse(NULL, [errorMsg UTF8String]); + } else { + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + didReceiveNotificationResponse([jsonString UTF8String], NULL); + } + + completionHandler(); +} + +@end + +static NotificationsDelegate *delegateInstance = nil; +static dispatch_once_t onceToken; + +bool ensureDelegateInitialized(void) { + __block BOOL success = YES; + + dispatch_once(&onceToken, ^{ + delegateInstance = [[NotificationsDelegate alloc] init]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = delegateInstance; + }); + + if (!delegateInstance) { + success = NO; + } + + return success; +} + +void requestNotificationAuthorization(int channelID) { + if (!ensureDelegateInitialized()) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; + + [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) { + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + } else { + captureResult(channelID, granted, NULL); + } + }]; +} + +void checkNotificationAuthorization(int channelID) { + if (!ensureDelegateInitialized()) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { + BOOL isAuthorized = (settings.authorizationStatus == UNAuthorizationStatusAuthorized); + captureResult(channelID, isAuthorized, NULL); + }]; +} + +// Helper function to create notification content +UNMutableNotificationContent* createNotificationContent(const char *title, const char *subtitle, + const char *body, const char *data_json, NSError **contentError) { + NSString *nsTitle = [NSString stringWithUTF8String:title]; + NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @""; + NSString *nsBody = [NSString stringWithUTF8String:body]; + + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.title = nsTitle; + if (![nsSubtitle isEqualToString:@""]) { + content.subtitle = nsSubtitle; + } + content.body = nsBody; + content.sound = [UNNotificationSound defaultSound]; + + // Parse JSON data if provided + if (data_json) { + NSString *dataJsonStr = [NSString stringWithUTF8String:data_json]; + NSData *jsonData = [dataJsonStr dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + if (!error && parsedData) { + content.userInfo = parsedData; + } else if (error) { + *contentError = error; + } + } + + return content; +} + +void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) { + if (!ensureDelegateInitialized()) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + + NSError *contentError = nil; + UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json, &contentError); + + if (contentError) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNTimeIntervalNotificationTrigger *trigger = nil; + + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger]; + + [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + } else { + captureResult(channelID, true, NULL); + } + }]; +} + +void sendNotificationWithActions(int channelID, const char *identifier, const char *title, const char *subtitle, + const char *body, const char *categoryId, const char *data_json) { + if (!ensureDelegateInitialized()) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + + NSError *contentError = nil; + UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json, &contentError); + + if (contentError) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + content.categoryIdentifier = nsCategoryId; + + UNTimeIntervalNotificationTrigger *trigger = nil; + + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger]; + + [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + } else { + captureResult(channelID, true, NULL); + } + }]; +} + +void registerNotificationCategory(int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, + const char *replyPlaceholder, const char *replyButtonTitle) { + if (!ensureDelegateInitialized()) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + NSString *actionsJsonStr = actions_json ? [NSString stringWithUTF8String:actions_json] : @"[]"; + + NSData *jsonData = [actionsJsonStr dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + NSArray *actionsArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSMutableArray *actions = [NSMutableArray array]; + + for (NSDictionary *actionDict in actionsArray) { + NSString *actionId = actionDict[@"id"]; + NSString *actionTitle = actionDict[@"title"]; + BOOL destructive = [actionDict[@"destructive"] boolValue]; + + if (actionId && actionTitle) { + UNNotificationActionOptions options = UNNotificationActionOptionNone; + if (destructive) options |= UNNotificationActionOptionDestructive; + + UNNotificationAction *action = [UNNotificationAction + actionWithIdentifier:actionId + title:actionTitle + options:options]; + [actions addObject:action]; + } + } + + if (hasReplyField && replyPlaceholder && replyButtonTitle) { + NSString *placeholder = [NSString stringWithUTF8String:replyPlaceholder]; + NSString *buttonTitle = [NSString stringWithUTF8String:replyButtonTitle]; + + UNTextInputNotificationAction *textAction = + [UNTextInputNotificationAction actionWithIdentifier:@"TEXT_REPLY" + title:buttonTitle + options:UNNotificationActionOptionNone + textInputButtonTitle:buttonTitle + textInputPlaceholder:placeholder]; + [actions addObject:textAction]; + } + + UNNotificationCategory *newCategory = [UNNotificationCategory + categoryWithIdentifier:nsCategoryId + actions:actions + intentIdentifiers:@[] + options:UNNotificationCategoryOptionNone]; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { + NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories]; + + // Remove existing category with same ID if it exists + UNNotificationCategory *existingCategory = nil; + for (UNNotificationCategory *category in updatedCategories) { + if ([category.identifier isEqualToString:nsCategoryId]) { + existingCategory = category; + break; + } + } + if (existingCategory) { + [updatedCategories removeObject:existingCategory]; + } + + // Add the new category + [updatedCategories addObject:newCategory]; + [center setNotificationCategories:updatedCategories]; + + captureResult(channelID, true, NULL); + }]; +} + +void removeNotificationCategory(int channelID, const char *categoryId) { + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { + NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories]; + + // Find and remove the category with matching identifier + UNNotificationCategory *categoryToRemove = nil; + for (UNNotificationCategory *category in updatedCategories) { + if ([category.identifier isEqualToString:nsCategoryId]) { + categoryToRemove = category; + break; + } + } + + if (categoryToRemove) { + [updatedCategories removeObject:categoryToRemove]; + [center setNotificationCategories:updatedCategories]; + captureResult(channelID, true, NULL); + } else { + NSString *errorMsg = [NSString stringWithFormat:@"Category '%@' not found", nsCategoryId]; + captureResult(channelID, false, [errorMsg UTF8String]); + } + }]; +} + +void removeAllPendingNotifications(void) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllPendingNotificationRequests]; +} + +void removePendingNotification(const char *identifier) { + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removePendingNotificationRequestsWithIdentifiers:@[nsIdentifier]]; +} + +void removeAllDeliveredNotifications(void) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllDeliveredNotifications]; +} + +void removeDeliveredNotification(const char *identifier) { + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeDeliveredNotificationsWithIdentifiers:@[nsIdentifier]]; +} \ No newline at end of file diff --git a/v3/pkg/services/notifications/notifications_linux.go b/v3/pkg/services/notifications/notifications_linux.go new file mode 100644 index 000000000..8dc9765cd --- /dev/null +++ b/v3/pkg/services/notifications/notifications_linux.go @@ -0,0 +1,565 @@ +//go:build linux + +package notifications + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type linuxNotifier struct { + conn *dbus.Conn + categories map[string]NotificationCategory + categoriesLock sync.RWMutex + notifications map[uint32]*notificationData + notificationsLock sync.RWMutex + appName string + cancel context.CancelFunc +} + +type notificationData struct { + ID string + Title string + Subtitle string + Body string + CategoryID string + Data map[string]interface{} + DBusID uint32 + ActionMap map[string]string +} + +const ( + dbusNotificationInterface = "org.freedesktop.Notifications" + dbusNotificationPath = "/org/freedesktop/Notifications" +) + +// Creates a new Notifications Service. +func New() *NotificationService { + notificationServiceOnce.Do(func() { + impl := &linuxNotifier{ + categories: make(map[string]NotificationCategory), + notifications: make(map[uint32]*notificationData), + } + + NotificationService_ = &NotificationService{ + impl: impl, + } + }) + + return NotificationService_ +} + +// Startup is called when the service is loaded. +func (ln *linuxNotifier) Startup(ctx context.Context, options application.ServiceOptions) error { + ln.appName = application.Get().Config().Name + + conn, err := dbus.ConnectSessionBus() + if err != nil { + return fmt.Errorf("failed to connect to session bus: %w", err) + } + ln.conn = conn + + if err := ln.loadCategories(); err != nil { + fmt.Printf("Failed to load notification categories: %v\n", err) + } + + var signalCtx context.Context + signalCtx, ln.cancel = context.WithCancel(context.Background()) + + if err := ln.setupSignalHandling(signalCtx); err != nil { + return fmt.Errorf("failed to set up notification signal handling: %w", err) + } + + return nil +} + +// Shutdown will save categories and close the D-Bus connection when the service unloads. +func (ln *linuxNotifier) Shutdown() error { + if ln.cancel != nil { + ln.cancel() + } + + if err := ln.saveCategories(); err != nil { + fmt.Printf("Failed to save notification categories: %v\n", err) + } + + if ln.conn != nil { + return ln.conn.Close() + } + return nil +} + +// RequestNotificationAuthorization is a Linux stub that always returns true, nil. +// (authorization is macOS-specific) +func (ln *linuxNotifier) RequestNotificationAuthorization() (bool, error) { + return true, nil +} + +// CheckNotificationAuthorization is a Linux stub that always returns true. +// (authorization is macOS-specific) +func (ln *linuxNotifier) CheckNotificationAuthorization() (bool, error) { + return true, nil +} + +// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body. +func (ln *linuxNotifier) SendNotification(options NotificationOptions) error { + hints := map[string]dbus.Variant{} + + body := options.Body + if options.Subtitle != "" { + body = options.Subtitle + "\n" + body + } + + defaultActionID := "default" + actions := []string{defaultActionID, "Default"} + + actionMap := map[string]string{ + defaultActionID: DefaultActionIdentifier, + } + + hints["x-notification-id"] = dbus.MakeVariant(options.ID) + + if options.Data != nil { + userData, err := json.Marshal(options.Data) + if err == nil { + hints["x-user-data"] = dbus.MakeVariant(string(userData)) + } + } + + // Call the Notify method on the D-Bus interface + obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call( + dbusNotificationInterface+".Notify", + 0, + ln.appName, + uint32(0), + "", // Icon + options.Title, + body, + actions, + hints, + int32(-1), + ) + + if call.Err != nil { + return fmt.Errorf("failed to send notification: %w", call.Err) + } + + var dbusID uint32 + if err := call.Store(&dbusID); err != nil { + return fmt.Errorf("failed to store notification ID: %w", err) + } + + notification := ¬ificationData{ + ID: options.ID, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + Data: options.Data, + DBusID: dbusID, + ActionMap: actionMap, + } + + ln.notificationsLock.Lock() + ln.notifications[dbusID] = notification + ln.notificationsLock.Unlock() + + return nil +} + +// SendNotificationWithActions sends a notification with additional actions. +func (ln *linuxNotifier) SendNotificationWithActions(options NotificationOptions) error { + ln.categoriesLock.RLock() + category, exists := ln.categories[options.CategoryID] + ln.categoriesLock.RUnlock() + + if options.CategoryID == "" || !exists { + // Fall back to basic notification + return ln.SendNotification(options) + } + + body := options.Body + if options.Subtitle != "" { + body = options.Subtitle + "\n" + body + } + + var actions []string + actionMap := make(map[string]string) + + defaultActionID := "default" + actions = append(actions, defaultActionID, "Default") + actionMap[defaultActionID] = DefaultActionIdentifier + + for _, action := range category.Actions { + actions = append(actions, action.ID, action.Title) + actionMap[action.ID] = action.ID + } + + hints := map[string]dbus.Variant{} + + hints["x-notification-id"] = dbus.MakeVariant(options.ID) + + hints["x-category-id"] = dbus.MakeVariant(options.CategoryID) + + if options.Data != nil { + userData, err := json.Marshal(options.Data) + if err == nil { + hints["x-user-data"] = dbus.MakeVariant(string(userData)) + } + } + + obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call( + dbusNotificationInterface+".Notify", + 0, + ln.appName, + uint32(0), + "", // Icon + options.Title, + body, + actions, + hints, + int32(-1), + ) + + if call.Err != nil { + return fmt.Errorf("failed to send notification: %w", call.Err) + } + + var dbusID uint32 + if err := call.Store(&dbusID); err != nil { + return fmt.Errorf("failed to store notification ID: %w", err) + } + + notification := ¬ificationData{ + ID: options.ID, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + CategoryID: options.CategoryID, + Data: options.Data, + DBusID: dbusID, + ActionMap: actionMap, + } + + ln.notificationsLock.Lock() + ln.notifications[dbusID] = notification + ln.notificationsLock.Unlock() + + return nil +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +func (ln *linuxNotifier) RegisterNotificationCategory(category NotificationCategory) error { + ln.categoriesLock.Lock() + ln.categories[category.ID] = category + ln.categoriesLock.Unlock() + + if err := ln.saveCategories(); err != nil { + fmt.Printf("Failed to save notification categories: %v\n", err) + } + + return nil +} + +// RemoveNotificationCategory removes a previously registered NotificationCategory. +func (ln *linuxNotifier) RemoveNotificationCategory(categoryId string) error { + ln.categoriesLock.Lock() + delete(ln.categories, categoryId) + ln.categoriesLock.Unlock() + + if err := ln.saveCategories(); err != nil { + fmt.Printf("Failed to save notification categories: %v\n", err) + } + + return nil +} + +// RemoveAllPendingNotifications attempts to remove all active notifications. +func (ln *linuxNotifier) RemoveAllPendingNotifications() error { + ln.notificationsLock.Lock() + dbusIDs := make([]uint32, 0, len(ln.notifications)) + for id := range ln.notifications { + dbusIDs = append(dbusIDs, id) + } + ln.notificationsLock.Unlock() + + for _, id := range dbusIDs { + ln.closeNotification(id) + } + + return nil +} + +// RemovePendingNotification removes a pending notification. +func (ln *linuxNotifier) RemovePendingNotification(identifier string) error { + var dbusID uint32 + found := false + + ln.notificationsLock.Lock() + for id, notif := range ln.notifications { + if notif.ID == identifier { + dbusID = id + found = true + break + } + } + ln.notificationsLock.Unlock() + + if !found { + return nil + } + + return ln.closeNotification(dbusID) +} + +// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux. +func (ln *linuxNotifier) RemoveAllDeliveredNotifications() error { + return ln.RemoveAllPendingNotifications() +} + +// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux. +func (ln *linuxNotifier) RemoveDeliveredNotification(identifier string) error { + return ln.RemovePendingNotification(identifier) +} + +// RemoveNotification removes a notification by identifier. +func (ln *linuxNotifier) RemoveNotification(identifier string) error { + return ln.RemovePendingNotification(identifier) +} + +// Helper method to close a notification. +func (ln *linuxNotifier) closeNotification(id uint32) error { + obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id) + + if call.Err != nil { + return fmt.Errorf("failed to close notification: %w", call.Err) + } + + return nil +} + +func (ln *linuxNotifier) getConfigDir() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", fmt.Errorf("failed to get user config directory: %w", err) + } + + appConfigDir := filepath.Join(configDir, ln.appName) + if err := os.MkdirAll(appConfigDir, 0755); err != nil { + return "", fmt.Errorf("failed to create app config directory: %w", err) + } + + return appConfigDir, nil +} + +// Save notification categories. +func (ln *linuxNotifier) saveCategories() error { + configDir, err := ln.getConfigDir() + if err != nil { + return err + } + + categoriesFile := filepath.Join(configDir, "notification-categories.json") + + ln.categoriesLock.RLock() + categoriesData, err := json.MarshalIndent(ln.categories, "", " ") + ln.categoriesLock.RUnlock() + + if err != nil { + return fmt.Errorf("failed to marshal notification categories: %w", err) + } + + if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil { + return fmt.Errorf("failed to write notification categories to disk: %w", err) + } + + return nil +} + +// Load notification categories. +func (ln *linuxNotifier) loadCategories() error { + configDir, err := ln.getConfigDir() + if err != nil { + return err + } + + categoriesFile := filepath.Join(configDir, "notification-categories.json") + + if _, err := os.Stat(categoriesFile); os.IsNotExist(err) { + return nil + } + + categoriesData, err := os.ReadFile(categoriesFile) + if err != nil { + return fmt.Errorf("failed to read notification categories from disk: %w", err) + } + + categories := make(map[string]NotificationCategory) + if err := json.Unmarshal(categoriesData, &categories); err != nil { + return fmt.Errorf("failed to unmarshal notification categories: %w", err) + } + + ln.categoriesLock.Lock() + ln.categories = categories + ln.categoriesLock.Unlock() + + return nil +} + +// Setup signal handling for notification actions. +func (ln *linuxNotifier) setupSignalHandling(ctx context.Context) error { + if err := ln.conn.AddMatchSignal( + dbus.WithMatchInterface(dbusNotificationInterface), + dbus.WithMatchMember("ActionInvoked"), + ); err != nil { + return err + } + + if err := ln.conn.AddMatchSignal( + dbus.WithMatchInterface(dbusNotificationInterface), + dbus.WithMatchMember("NotificationClosed"), + ); err != nil { + return err + } + + c := make(chan *dbus.Signal, 10) + ln.conn.Signal(c) + + go ln.handleSignals(ctx, c) + + return nil +} + +// Handle incoming D-Bus signals. +func (ln *linuxNotifier) handleSignals(ctx context.Context, c chan *dbus.Signal) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-c: + if !ok { + return + } + + switch signal.Name { + case dbusNotificationInterface + ".ActionInvoked": + ln.handleActionInvoked(signal) + case dbusNotificationInterface + ".NotificationClosed": + ln.handleNotificationClosed(signal) + } + } + } +} + +// Handle ActionInvoked signal. +func (ln *linuxNotifier) handleActionInvoked(signal *dbus.Signal) { + if len(signal.Body) < 2 { + return + } + + dbusID, ok := signal.Body[0].(uint32) + if !ok { + return + } + + actionID, ok := signal.Body[1].(string) + if !ok { + return + } + + ln.notificationsLock.Lock() + notification, exists := ln.notifications[dbusID] + if exists { + delete(ln.notifications, dbusID) + } + ln.notificationsLock.Unlock() + + if !exists { + return + } + + appActionID, ok := notification.ActionMap[actionID] + if !ok { + appActionID = actionID + } + + response := NotificationResponse{ + ID: notification.ID, + ActionIdentifier: appActionID, + Title: notification.Title, + Subtitle: notification.Subtitle, + Body: notification.Body, + CategoryID: notification.CategoryID, + UserInfo: notification.Data, + } + + result := NotificationResult{ + Response: response, + } + + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } +} + +// Handle NotificationClosed signal. +// Reason codes: +// 1 - expired timeout +// 2 - dismissed by user (click on X) +// 3 - closed by CloseNotification call +// 4 - undefined/reserved +func (ln *linuxNotifier) handleNotificationClosed(signal *dbus.Signal) { + if len(signal.Body) < 2 { + return + } + + dbusID, ok := signal.Body[0].(uint32) + if !ok { + return + } + + reason, ok := signal.Body[1].(uint32) + if !ok { + reason = 0 // Unknown reason + } + + ln.notificationsLock.Lock() + notification, exists := ln.notifications[dbusID] + if exists { + delete(ln.notifications, dbusID) + } + ln.notificationsLock.Unlock() + + if !exists { + return + } + + if reason == 2 { + response := NotificationResponse{ + ID: notification.ID, + ActionIdentifier: DefaultActionIdentifier, + Title: notification.Title, + Subtitle: notification.Subtitle, + Body: notification.Body, + CategoryID: notification.CategoryID, + UserInfo: notification.Data, + } + + result := NotificationResult{ + Response: response, + } + + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } + } +} diff --git a/v3/pkg/services/notifications/notifications_windows.go b/v3/pkg/services/notifications/notifications_windows.go new file mode 100644 index 000000000..1a1a6dd85 --- /dev/null +++ b/v3/pkg/services/notifications/notifications_windows.go @@ -0,0 +1,459 @@ +//go:build windows + +package notifications + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + _ "unsafe" + + "git.sr.ht/~jackmordaunt/go-toast/v2" + wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" + "github.com/google/uuid" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" + "golang.org/x/sys/windows/registry" +) + +type windowsNotifier struct { + categories map[string]NotificationCategory + categoriesLock sync.RWMutex + appName string + appGUID string + iconPath string + exePath string +} + +const ( + ToastRegistryPath = `Software\Classes\AppUserModelId\` + ToastRegistryGuidKey = "CustomActivator" + NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories` + NotificationCategoriesRegistryKey = "Categories" +) + +// NotificationPayload combines the action ID and user data into a single structure +type NotificationPayload struct { + Action string `json:"action"` + Options NotificationOptions `json:"payload,omitempty"` +} + +// Creates a new Notifications Service. +func New() *NotificationService { + notificationServiceOnce.Do(func() { + impl := &windowsNotifier{ + categories: make(map[string]NotificationCategory), + } + + NotificationService_ = &NotificationService{ + impl: impl, + } + }) + + return NotificationService_ +} + +//go:linkname registerFactoryInternal git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory +func registerFactoryInternal(factory *wintoast.IClassFactory) error + +// Startup is called when the service is loaded +// Sets an activation callback to emit an event when notifications are interacted with. +func (wn *windowsNotifier) Startup(ctx context.Context, options application.ServiceOptions) error { + wn.categoriesLock.Lock() + defer wn.categoriesLock.Unlock() + + app := application.Get() + cfg := app.Config() + + wn.appName = cfg.Name + + guid, err := wn.getGUID() + if err != nil { + return err + } + wn.appGUID = guid + + wn.iconPath = filepath.Join(os.TempDir(), wn.appName+wn.appGUID+".png") + + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + wn.exePath = exe + + // Create the registry key for the toast activator + key, _, err := registry.CreateKey(registry.CURRENT_USER, + `Software\Classes\CLSID\`+wn.appGUID+`\LocalServer32`, registry.ALL_ACCESS) + if err != nil { + return fmt.Errorf("failed to create CLSID key: %w", err) + } + + if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", wn.exePath)); err != nil { + return fmt.Errorf("failed to set CLSID server path: %w", err) + } + key.Close() + + toast.SetAppData(toast.AppData{ + AppID: wn.appName, + GUID: guid, + IconPath: wn.iconPath, + ActivationExe: wn.exePath, + }) + + toast.SetActivationCallback(func(args string, data []toast.UserData) { + result := NotificationResult{} + + actionIdentifier, options, err := parseNotificationResponse(args) + + if err != nil { + result.Error = err + } else { + // Subtitle is retained but was not shown with the notification + response := NotificationResponse{ + ID: options.ID, + ActionIdentifier: actionIdentifier, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + CategoryID: options.CategoryID, + UserInfo: options.Data, + } + + if userText, found := wn.getUserText(data); found { + response.UserText = userText + } + + result.Response = response + } + + if ns := getNotificationService(); ns != nil { + ns.handleNotificationResult(result) + } + }) + + // Register the class factory for the toast activator + if err := registerFactoryInternal(wintoast.ClassFactory); err != nil { + return fmt.Errorf("CoRegisterClassObject failed: %w", err) + } + + return wn.loadCategoriesFromRegistry() +} + +// Shutdown will attempt to save the categories to the registry when the service unloads +func (wn *windowsNotifier) Shutdown() error { + wn.categoriesLock.Lock() + defer wn.categoriesLock.Unlock() + + return wn.saveCategoriesToRegistry() +} + +// RequestNotificationAuthorization is a Windows stub that always returns true, nil. +// (user authorization is macOS-specific) +func (wn *windowsNotifier) RequestNotificationAuthorization() (bool, error) { + return true, nil +} + +// CheckNotificationAuthorization is a Windows stub that always returns true. +// (user authorization is macOS-specific) +func (wn *windowsNotifier) CheckNotificationAuthorization() (bool, error) { + return true, nil +} + +// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows. +// (subtitle is only available on macOS and Linux) +func (wn *windowsNotifier) SendNotification(options NotificationOptions) error { + if err := wn.saveIconToDir(); err != nil { + fmt.Printf("Error saving icon: %v\n", err) + } + + n := toast.Notification{ + Title: options.Title, + Body: options.Body, + ActivationType: toast.Foreground, + ActivationArguments: DefaultActionIdentifier, + } + + encodedPayload, err := wn.encodePayload(DefaultActionIdentifier, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.ActivationArguments = encodedPayload + + return n.Push() +} + +// SendNotificationWithActions sends a notification with additional actions and inputs. +// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category. +// If a NotificationCategory is not registered a basic notification will be sent. +// (subtitle is only available on macOS and Linux) +func (wn *windowsNotifier) SendNotificationWithActions(options NotificationOptions) error { + if err := wn.saveIconToDir(); err != nil { + fmt.Printf("Error saving icon: %v\n", err) + } + + wn.categoriesLock.RLock() + nCategory, categoryExists := wn.categories[options.CategoryID] + wn.categoriesLock.RUnlock() + + if options.CategoryID == "" || !categoryExists { + fmt.Printf("Category '%s' not found, sending basic notification without actions\n", options.CategoryID) + } + + n := toast.Notification{ + Title: options.Title, + Body: options.Body, + ActivationType: toast.Foreground, + ActivationArguments: DefaultActionIdentifier, + } + + for _, action := range nCategory.Actions { + n.Actions = append(n.Actions, toast.Action{ + Content: action.Title, + Arguments: action.ID, + }) + } + + if nCategory.HasReplyField { + n.Inputs = append(n.Inputs, toast.Input{ + ID: "userText", + Placeholder: nCategory.ReplyPlaceholder, + }) + + n.Actions = append(n.Actions, toast.Action{ + Content: nCategory.ReplyButtonTitle, + Arguments: "TEXT_REPLY", + InputID: "userText", + }) + } + + encodedPayload, err := wn.encodePayload(n.ActivationArguments, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.ActivationArguments = encodedPayload + + for index := range n.Actions { + encodedPayload, err := wn.encodePayload(n.Actions[index].Arguments, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.Actions[index].Arguments = encodedPayload + } + + return n.Push() +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +// Registering a category with the same name as a previously registered NotificationCategory will override it. +func (wn *windowsNotifier) RegisterNotificationCategory(category NotificationCategory) error { + wn.categoriesLock.Lock() + defer wn.categoriesLock.Unlock() + + wn.categories[category.ID] = NotificationCategory{ + ID: category.ID, + Actions: category.Actions, + HasReplyField: bool(category.HasReplyField), + ReplyPlaceholder: category.ReplyPlaceholder, + ReplyButtonTitle: category.ReplyButtonTitle, + } + + return wn.saveCategoriesToRegistry() +} + +// RemoveNotificationCategory removes a previously registered NotificationCategory. +func (wn *windowsNotifier) RemoveNotificationCategory(categoryId string) error { + wn.categoriesLock.Lock() + defer wn.categoriesLock.Unlock() + + delete(wn.categories, categoryId) + + return wn.saveCategoriesToRegistry() +} + +// RemoveAllPendingNotifications is a Windows stub that always returns nil. +// (macOS and Linux only) +func (wn *windowsNotifier) RemoveAllPendingNotifications() error { + return nil +} + +// RemovePendingNotification is a Windows stub that always returns nil. +// (macOS and Linux only) +func (wn *windowsNotifier) RemovePendingNotification(_ string) error { + return nil +} + +// RemoveAllDeliveredNotifications is a Windows stub that always returns nil. +// (macOS and Linux only) +func (wn *windowsNotifier) RemoveAllDeliveredNotifications() error { + return nil +} + +// RemoveDeliveredNotification is a Windows stub that always returns nil. +// (macOS and Linux only) +func (wn *windowsNotifier) RemoveDeliveredNotification(_ string) error { + return nil +} + +// RemoveNotification is a Windows stub that always returns nil. +// (Linux-specific) +func (wn *windowsNotifier) RemoveNotification(identifier string) error { + return nil +} + +// encodePayload combines an action ID and user data into a single encoded string +func (wn *windowsNotifier) encodePayload(actionID string, options NotificationOptions) (string, error) { + payload := NotificationPayload{ + Action: actionID, + Options: options, + } + + jsonData, err := json.Marshal(payload) + if err != nil { + return actionID, err + } + + encodedPayload := base64.StdEncoding.EncodeToString(jsonData) + return encodedPayload, nil +} + +// decodePayload extracts the action ID and user data from an encoded payload +func decodePayload(encodedString string) (string, NotificationOptions, error) { + jsonData, err := base64.StdEncoding.DecodeString(encodedString) + if err != nil { + return encodedString, NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err) + } + + var payload NotificationPayload + if err := json.Unmarshal(jsonData, &payload); err != nil { + return encodedString, NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err) + } + + return payload.Action, payload.Options, nil +} + +// parseNotificationResponse updated to use structured payload decoding +func parseNotificationResponse(response string) (action string, options NotificationOptions, err error) { + actionID, options, err := decodePayload(response) + + if err != nil { + fmt.Printf("Warning: Failed to decode notification response: %v\n", err) + return response, NotificationOptions{}, err + } + + return actionID, options, nil +} + +func (wn *windowsNotifier) saveIconToDir() error { + icon, err := application.NewIconFromResource(w32.GetModuleHandle(""), uint16(3)) + if err != nil { + return fmt.Errorf("failed to retrieve application icon: %w", err) + } + + return w32.SaveHIconAsPNG(icon, wn.iconPath) +} + +func (wn *windowsNotifier) saveCategoriesToRegistry() error { + // We assume lock is held by caller + + registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName) + + key, _, err := registry.CreateKey( + registry.CURRENT_USER, + registryPath, + registry.ALL_ACCESS, + ) + if err != nil { + return err + } + defer key.Close() + + data, err := json.Marshal(wn.categories) + if err != nil { + return err + } + + return key.SetStringValue(NotificationCategoriesRegistryKey, string(data)) +} + +func (wn *windowsNotifier) loadCategoriesFromRegistry() error { + // We assume lock is held by caller + + registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName) + + key, err := registry.OpenKey( + registry.CURRENT_USER, + registryPath, + registry.QUERY_VALUE, + ) + if err != nil { + if err == registry.ErrNotExist { + // Not an error, no saved categories + return nil + } + return fmt.Errorf("failed to open registry key: %w", err) + } + defer key.Close() + + data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey) + if err != nil { + if err == registry.ErrNotExist { + // No value yet, but key exists + return nil + } + return fmt.Errorf("failed to read categories from registry: %w", err) + } + + categories := make(map[string]NotificationCategory) + if err := json.Unmarshal([]byte(data), &categories); err != nil { + return fmt.Errorf("failed to parse notification categories from registry: %w", err) + } + + wn.categories = categories + + return nil +} + +func (wn *windowsNotifier) getUserText(data []toast.UserData) (string, bool) { + for _, d := range data { + if d.Key == "userText" { + return d.Value, true + } + } + return "", false +} + +func (wn *windowsNotifier) getGUID() (string, error) { + keyPath := ToastRegistryPath + wn.appName + + k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE) + if err == nil { + guid, _, err := k.GetStringValue(ToastRegistryGuidKey) + k.Close() + if err == nil && guid != "" { + return guid, nil + } + } + + guid := wn.generateGUID() + + k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE) + if err != nil { + return "", fmt.Errorf("failed to create registry key: %w", err) + } + defer k.Close() + + if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil { + return "", fmt.Errorf("failed to write GUID to registry: %w", err) + } + + return guid, nil +} + +func (wn *windowsNotifier) generateGUID() string { + guid := uuid.New() + return fmt.Sprintf("{%s}", guid.String()) +} diff --git a/v3/pkg/services/sqlite/sqlite.go b/v3/pkg/services/sqlite/sqlite.go new file mode 100644 index 000000000..daf3ecd05 --- /dev/null +++ b/v3/pkg/services/sqlite/sqlite.go @@ -0,0 +1,500 @@ +//wails:include stmt.js +package sqlite + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "sync" + "sync/atomic" + + "github.com/pkg/errors" + "github.com/wailsapp/wails/v3/pkg/application" + _ "modernc.org/sqlite" +) + +type Config struct { + // DBSource is the database URI to use. + // The string ":memory:" can be used to create an in-memory database. + // The sqlite driver can be configured through query parameters. + // For more details see https://pkg.go.dev/modernc.org/sqlite#Driver.Open + DBSource string +} + +//wails:inject export { +//wails:inject ExecContext as Execute, +//wails:inject QueryContext as Query +//wails:inject }; +//wails:inject +//wails:inject import { Stmt } from "./stmt.js"; +//wails:inject +//wails:inject **:/** +//wails:inject **: * Prepare creates a prepared statement for later queries or executions. +//wails:inject **: * Multiple queries or executions may be run concurrently from the returned statement. +//wails:inject **: * +//wails:inject **: * The caller must call the statement's Close method when it is no longer needed. +//wails:inject **: * Statements are closed automatically +//wails:inject **: * when the connection they are associated with is closed. +//wails:inject **: * +//wails:inject **: * Prepare supports early cancellation. +//wails:inject j*: * +//wails:inject j*: * @param {string} query +//wails:inject j*: * @returns {Promise & { cancel(): void }} +//wails:inject **: */ +//wails:inject j*:export function Prepare(query) { +//wails:inject t*:export function Prepare(query: string): Promise & { cancel(): void } { +//wails:inject **: const promise = PrepareContext(query); +//wails:inject j*: const wrapper = /** @type {any} */(promise.then(function (id) { +//wails:inject t*: const wrapper: any = (promise.then(function (id) { +//wails:inject **: return id == null ? null : new Stmt( +//wails:inject **: ClosePrepared.bind(null, id), +//wails:inject **: ExecPrepared.bind(null, id), +//wails:inject **: QueryPrepared.bind(null, id)); +//wails:inject **: })); +//wails:inject **: wrapper.cancel = promise.cancel; +//wails:inject **: return wrapper; +//wails:inject **:} +type SQLiteService struct { + lock sync.RWMutex + config *Config + conn *sql.DB + stmts map[uint64]struct{} +} + +// New initialises a sqlite service with the default configuration. +func New() *SQLiteService { + return NewWithConfig(nil) +} + +// NewWithConfig initialises a sqlite service with a custom configuration. +// If config is nil, it falls back to the default configuration, i.e. an in-memory database. +// +// The database connection is not opened right away. +// A call to [Service.Open] must succeed before using all other methods. +// If the service is registered with the application, +// [Service.Open] will be called automatically at startup. +func NewWithConfig(config *Config) *SQLiteService { + result := &SQLiteService{} + result.Configure(config) + return result +} + +// ServiceName returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (s *SQLiteService) ServiceName() string { + return "github.com/wailsapp/wails/v3/plugins/sqlite" +} + +// ServiceStartup opens the database connection. +// It returns a non-nil error in case of failures. +func (s *SQLiteService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + return s.Open() +} + +// ServiceShutdown closes the database connection. +// It returns a non-nil error in case of failures. +func (s *SQLiteService) ServiceShutdown() error { + return s.Close() +} + +// Configure changes the database service configuration. +// The connection state at call time is preserved. +// Consumers will need to call [Service.Open] manually after Configure +// in order to reconnect with the new configuration. +// +// See [NewWithConfig] for details on configuration. +// +//wails:ignore +func (s *SQLiteService) Configure(config *Config) { + if config == nil { + config = &Config{DBSource: ":memory:"} + } else { + // Clone to prevent changes from the outside. + clone := new(Config) + *clone = *config + config = clone + } + + s.lock.Lock() + defer s.lock.Unlock() + + s.config = config +} + +// Open validates the current configuration, +// closes the current connection if one is present, +// then opens and validates a new connection. +// +// Even when a non-nil error is returned, +// the database service is left in a consistent state, +// ready for a new call to Open. +func (s *SQLiteService) Open() error { + s.lock.Lock() + defer s.lock.Unlock() + + if s.config.DBSource == "" { + return errors.New(`no database source specified; please set DBSource in the config to a filename or specify ":memory:" to use an in-memory database`) + } + + if err := s.closeImpl(); err != nil { + return err + } + + conn, err := sql.Open("sqlite", s.config.DBSource) + if err != nil { + return errors.Wrap(err, "error opening database connection") + } + + // Test connection + if err := conn.Ping(); err != nil { + _ = conn.Close() + return errors.Wrap(err, "error opening database connection") + } + + s.conn = conn + s.stmts = make(map[uint64]struct{}) + + return nil +} + +// Close closes the current database connection if one is open, otherwise has no effect. +// Additionally, Close closes all open prepared statements associated to the connection. +// +// Even when a non-nil error is returned, +// the database service is left in a consistent state, +// ready for a call to [Service.Open]. +func (s *SQLiteService) Close() error { + s.lock.Lock() + defer s.lock.Unlock() + + return s.closeImpl() +} + +// closeImpl performs the close operation without acquiring the lock first. +// It is the caller's responsibility +// to ensure the lock is held exclusively (in write mode) +// for the entire duration of the call. +func (s *SQLiteService) closeImpl() error { + if s.conn == nil { + return nil + } + + for id := range s.stmts { + if stmt, ok := stmts.Load(id); ok { + // WARN: do not delegate to [Stmt.Close], it would cause a deadlock. + // Ignore errors, closing the connection should free up all resources. + _ = stmt.(*Stmt).sqlStmt.Close() + } + } + + err := s.conn.Close() + + // Clear the connection even in case of errors: + // if [sql.DB.Close] returns an error, + // the connection becomes unusable. + s.conn = nil + s.stmts = nil + + return err +} + +// Execute executes a query without returning any rows. +// +//wails:ignore +func (s *SQLiteService) Execute(query string, args ...any) error { + return s.ExecContext(context.Background(), query, args...) +} + +// ExecContext executes a query without returning any rows. +// It supports early cancellation. +// +//wails:internal +func (s *SQLiteService) ExecContext(ctx context.Context, query string, args ...any) error { + s.lock.RLock() + conn := s.conn + s.lock.RUnlock() + + if conn == nil { + return errors.New("no open database connection") + } + + _, err := conn.ExecContext(ctx, query, args...) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + + return nil +} + +// Query executes a query and returns a slice of key-value records, +// one per row, with column names as keys. +// +//wails:ignore +func (s *SQLiteService) Query(query string, args ...any) (Rows, error) { + return s.QueryContext(context.Background(), query, args...) +} + +// QueryContext executes a query and returns a slice of key-value records, +// one per row, with column names as keys. +// It supports early cancellation, returning the slice of results fetched so far. +// +//wails:internal +func (s *SQLiteService) QueryContext(ctx context.Context, query string, args ...any) (Rows, error) { + s.lock.RLock() + conn := s.conn + s.lock.RUnlock() + + if conn == nil { + return nil, errors.New("no open database connection") + } + + rows, err := conn.QueryContext(ctx, query, args...) + if err != nil { + if errors.Is(err, context.Canceled) { + return Rows{}, nil + } else { + return nil, err + } + } + + return parseRows(ctx, rows) +} + +// Prepare creates a prepared statement for later queries or executions. +// Multiple queries or executions may be run concurrently from the returned statement. +// +// The caller should call the statement's Close method when it is no longer needed. +// Statements are closed automatically +// when the connection they are associated with is closed. +// +//wails:ignore +func (s *SQLiteService) Prepare(query string) (*Stmt, error) { + return s.PrepareContext(context.Background(), query) +} + +// PrepareContext creates a prepared statement for later queries or executions. +// Multiple queries or executions may be run concurrently from the returned statement. +// +// The caller must call the statement's Close method when it is no longer needed. +// Statements are closed automatically +// when the connection they are associated with is closed. +// +// PrepareContext supports early cancellation. +// +//wails:internal +func (s *SQLiteService) PrepareContext(ctx context.Context, query string) (*Stmt, error) { + s.lock.RLock() + conn := s.conn + s.lock.RUnlock() + + if conn == nil { + return nil, errors.New("no open database connection") + } + + id := nextId.Load() + for id != 0 && !nextId.CompareAndSwap(id, id+1) { + } + if id == 0 { + return nil, errors.New("prepared statement ids exhausted") + } + + stmt, err := conn.PrepareContext(ctx, query) + if err != nil { + if errors.Is(err, context.Canceled) { + return nil, nil + } else { + return nil, err + } + } + + func() { + s.lock.Lock() + defer s.lock.Unlock() + + s.stmts[id] = struct{}{} + }() + + wrapper := &Stmt{ + sqlStmt: stmt, + db: s, + id: id, + } + stmts.Store(id, wrapper) + + return wrapper, nil +} + +// ClosePrepared closes a prepared statement +// obtained with [Service.Prepare] or [Service.PrepareContext]. +// ClosePrepared is idempotent: +// it has no effect on prepared statements that are already closed. +// +//wails:internal +func (s *SQLiteService) ClosePrepared(stmt *Stmt) error { + return stmt.Close() +} + +// ExecPrepared executes a prepared statement +// obtained with [Service.Prepare] or [Service.PrepareContext] +// without returning any rows. +// It supports early cancellation. +// +//wails:internal +func (s *SQLiteService) ExecPrepared(ctx context.Context, stmt *Stmt, args ...any) error { + if stmt == nil { + return errors.New("no prepared statement provided") + } else if stmt.sqlStmt == nil { + return errors.New("prepared statement is not valid") + } + + _, err := stmt.ExecContext(ctx, args...) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + + return nil +} + +// QueryPrepared executes a prepared statement +// obtained with [Service.Prepare] or [Service.PrepareContext] +// and returns a slice of key-value records, one per row, with column names as keys. +// It supports early cancellation, returning the slice of results fetched so far. +// +//wails:internal +func (s *SQLiteService) QueryPrepared(ctx context.Context, stmt *Stmt, args ...any) (Rows, error) { + if stmt == nil { + return nil, errors.New("no prepared statement provided") + } else if stmt.sqlStmt == nil { + return nil, errors.New("prepared statement is not valid") + } + + rows, err := stmt.sqlStmt.QueryContext(ctx, args...) + if err != nil { + if errors.Is(err, context.Canceled) { + return Rows{}, nil + } else { + return nil, err + } + } + + return parseRows(ctx, rows) +} + +type ( + // Row holds a single row in the result of a query. + // It is a key-value map where keys are column names. + Row = map[string]any + + // Rows holds the result of a query + // as an array of key-value maps where keys are column names. + Rows = []Row +) + +func parseRows(ctx context.Context, rows *sql.Rows) (Rows, error) { + defer rows.Close() + + columns, _ := rows.Columns() + values := make([]any, len(columns)) + pointers := make([]any, len(columns)) + results := []map[string]any{} + + for rows.Next() { + select { + default: + case <-ctx.Done(): + return results, nil + } + + for i := range values { + pointers[i] = &values[i] + } + + if err := rows.Scan(pointers...); err != nil { + return nil, err + } + + row := make(map[string]any, len(columns)) + for i, column := range columns { + row[column] = values[i] + } + + results = append(results, row) + } + + return results, nil +} + +var ( + // stmts holds all currently active prepared statements, + // for all [Service] instances. + stmts sync.Map + + // nextId holds the next available prepared statement id. + // We use a counter to make sure IDs are never reused. + nextId atomic.Uint64 +) + +func init() { + nextId.Store(1) +} + +type ( + sqlStmt = *sql.Stmt + + // Stmt wraps a prepared sql statement pointer. + // It provides the same methods as the [sql.Stmt] type. + // + //wails:internal + Stmt struct { + sqlStmt + db *SQLiteService + id uint64 + } +) + +// Close closes the statement. +// It has no effect when the statement is already closed. +func (s *Stmt) Close() error { + if s == nil || s.sqlStmt == nil { + return nil + } + + err := s.sqlStmt.Close() + stmts.Delete(s.id) + + func() { + s.db.lock.Lock() + defer s.db.lock.Unlock() + + delete(s.db.stmts, s.id) + }() + + return errors.Wrap(err, "error closing prepared statement") +} + +func (s *Stmt) MarshalText() ([]byte, error) { + var buf bytes.Buffer + buf.Grow(16) + + if _, err := fmt.Fprintf(&buf, "%016x", s.id); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (s *Stmt) UnmarshalText(text []byte) error { + if n, err := fmt.Fscanf(bytes.NewReader(text), "%x", &s.id); n < 1 || err != nil { + return errors.New("invalid prepared statement id") + } + + if stmt, ok := stmts.Load(s.id); ok { + *s = *(stmt.(*Stmt)) + } else { + s.sqlStmt = nil + s.db = nil + } + + return nil +} diff --git a/v3/pkg/services/sqlite/stmt.js b/v3/pkg/services/sqlite/stmt.js new file mode 100644 index 000000000..948b0c3dd --- /dev/null +++ b/v3/pkg/services/sqlite/stmt.js @@ -0,0 +1,79 @@ +//@ts-check + +//@ts-ignore: Unused imports +import * as $models from "./models.js"; + +const execSymbol = Symbol("exec"), + querySymbol = Symbol("query"), + closeSymbol = Symbol("close"); + +/** + * Stmt represents a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently on the same statement. + * + * The caller must call the statement's Close method when it is no longer needed. + * Statements are closed automatically + * when the connection they are associated with is closed. + */ +export class Stmt { + /** + * Constructs a new prepared statement instance. + * @param {(...args: any[]) => Promise} close + * @param {(...args: any[]) => Promise & { cancel(): void }} exec + * @param {(...args: any[]) => Promise<$models.Rows> & { cancel(): void }} query + */ + constructor(close, exec, query) { + /** + * @member + * @private + * @type {typeof close} + */ + this[closeSymbol] = close; + + /** + * @member + * @private + * @type {typeof exec} + */ + this[execSymbol] = exec; + + /** + * @member + * @private + * @type {typeof query} + */ + this[querySymbol] = query; + } + + /** + * Closes the prepared statement. + * It has no effect when the statement is already closed. + * @returns {Promise} + */ + Close() { + return this[closeSymbol](); + } + + /** + * Executes the prepared statement without returning any rows. + * It supports early cancellation. + * + * @param {any[]} args + * @returns {Promise & { cancel(): void }} + */ + Exec(...args) { + return this[execSymbol](...args); + } + + /** + * Executes the prepared statement + * and returns a slice of key-value records, one per row, with column names as keys. + * It supports early cancellation, returning the array of results fetched so far. + * + * @param {any[]} args + * @returns {Promise<$models.Rows> & { cancel(): void }} + */ + Query(...args) { + return this[querySymbol](...args); + } +} diff --git a/v3/pkg/w32/actions.go b/v3/pkg/w32/actions.go new file mode 100644 index 000000000..2d376c7f4 --- /dev/null +++ b/v3/pkg/w32/actions.go @@ -0,0 +1,27 @@ +//go:build windows + +package w32 + +func Undo(hwnd HWND) { + SendMessage(hwnd, WM_UNDO, 0, 0) +} + +func Cut(hwnd HWND) { + SendMessage(hwnd, WM_CUT, 0, 0) +} + +func Copy(hwnd HWND) { + SendMessage(hwnd, WM_COPY, 0, 0) +} + +func Paste(hwnd HWND) { + SendMessage(hwnd, WM_PASTE, 0, 0) +} + +func Delete(hwnd HWND) { + SendMessage(hwnd, WM_CLEAR, 0, 0) +} + +func SelectAll(hwnd HWND) { + SendMessage(hwnd, WM_SELECTALL, 0, 0) +} diff --git a/v3/pkg/w32/clipboard.go b/v3/pkg/w32/clipboard.go new file mode 100644 index 000000000..89334c0a4 --- /dev/null +++ b/v3/pkg/w32/clipboard.go @@ -0,0 +1,143 @@ +//go:build windows + +/* + * Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved. + */ + +package w32 + +import ( + "runtime" + "syscall" + "time" + "unsafe" +) + +const ( + cfUnicodetext = 13 + gmemMoveable = 0x0002 +) + +// waitOpenClipboard opens the clipboard, waiting for up to a second to do so. +func waitOpenClipboard() error { + started := time.Now() + limit := started.Add(time.Second) + var r uintptr + var err error + for time.Now().Before(limit) { + r, _, err = procOpenClipboard.Call(0) + if r != 0 { + return nil + } + time.Sleep(time.Millisecond) + } + return err +} + +func GetClipboardText() (string, error) { + // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). + // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { + return "", err + } + err := waitOpenClipboard() + if err != nil { + return "", err + } + + h, _, err := procGetClipboardData.Call(cfUnicodetext) + if h == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + l, _, err := kernelGlobalLock.Call(h) + if l == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) + + r, _, err := kernelGlobalUnlock.Call(h) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return "", err + } + + closed, _, err := procCloseClipboard.Call() + if closed == 0 { + return "", err + } + return text, nil +} + +func SetClipboardText(text string) error { + // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). + // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := waitOpenClipboard() + if err != nil { + return err + } + + r, _, err := procEmptyClipboard.Call(0) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + data, err := syscall.UTF16FromString(text) + if err != nil { + return err + } + + // "If the hMem parameter identifies a memory object, the object must have + // been allocated using the function with the GMEM_MOVEABLE flag." + h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) + if h == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + defer func() { + if h != 0 { + kernelGlobalFree.Call(h) + } + }() + + l, _, err := kernelGlobalLock.Call(h) + if l == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + + r, _, err = kernelGlobalUnlock.Call(h) + if r == 0 { + if err.(syscall.Errno) != 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + } + + r, _, err = procSetClipboardData.Call(cfUnicodetext, h) + if r == 0 { + _, _, _ = procCloseClipboard.Call() + return err + } + h = 0 // suppress deferred cleanup + closed, _, err := procCloseClipboard.Call() + if closed == 0 { + return err + } + return nil +} diff --git a/v3/pkg/w32/com.go b/v3/pkg/w32/com.go new file mode 100644 index 000000000..35b79e535 --- /dev/null +++ b/v3/pkg/w32/com.go @@ -0,0 +1,55 @@ +//go:build windows + +package w32 + +import ( + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +// ComProc stores a COM procedure. +type ComProc uintptr + +// NewComProc creates a new COM proc from a Go function. +func NewComProc(fn interface{}) ComProc { + return ComProc(windows.NewCallback(fn)) +} + +type EventRegistrationToken struct { + value int64 +} + +// IUnknown +type IUnknown struct { + Vtbl *IUnknownVtbl +} + +type IUnknownVtbl struct { + QueryInterface ComProc + AddRef ComProc + Release ComProc +} + +func (i *IUnknownVtbl) CallRelease(this unsafe.Pointer) error { + _, _, err := i.Release.Call( + uintptr(this), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +type IUnknownImpl interface { + QueryInterface(refiid, object uintptr) uintptr + AddRef() uintptr + Release() uintptr +} + +// Call calls a COM procedure. +// +//go:uintptrescapes +func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { + return syscall.SyscallN(uintptr(p), a...) +} diff --git a/v3/pkg/w32/comctl32.go b/v3/pkg/w32/comctl32.go new file mode 100644 index 000000000..b66709f5f --- /dev/null +++ b/v3/pkg/w32/comctl32.go @@ -0,0 +1,112 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modcomctl32 = syscall.NewLazyDLL("comctl32.dll") + + procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx") + procImageList_Create = modcomctl32.NewProc("ImageList_Create") + procImageList_Destroy = modcomctl32.NewProc("ImageList_Destroy") + procImageList_GetImageCount = modcomctl32.NewProc("ImageList_GetImageCount") + procImageList_SetImageCount = modcomctl32.NewProc("ImageList_SetImageCount") + procImageList_Add = modcomctl32.NewProc("ImageList_Add") + procImageList_ReplaceIcon = modcomctl32.NewProc("ImageList_ReplaceIcon") + procImageList_Remove = modcomctl32.NewProc("ImageList_Remove") + procTrackMouseEvent = modcomctl32.NewProc("_TrackMouseEvent") +) + +func InitCommonControlsEx(lpInitCtrls *INITCOMMONCONTROLSEX) bool { + ret, _, _ := procInitCommonControlsEx.Call( + uintptr(unsafe.Pointer(lpInitCtrls))) + + return ret != 0 +} + +func ImageList_Create(cx, cy int, flags uint, cInitial, cGrow int) HIMAGELIST { + ret, _, _ := procImageList_Create.Call( + uintptr(cx), + uintptr(cy), + uintptr(flags), + uintptr(cInitial), + uintptr(cGrow)) + + if ret == 0 { + panic("Create image list failed") + } + + return HIMAGELIST(ret) +} + +func ImageList_Destroy(himl HIMAGELIST) bool { + ret, _, _ := procImageList_Destroy.Call( + uintptr(himl)) + + return ret != 0 +} + +func ImageList_GetImageCount(himl HIMAGELIST) int { + ret, _, _ := procImageList_GetImageCount.Call( + uintptr(himl)) + + return int(ret) +} + +func ImageList_SetImageCount(himl HIMAGELIST, uNewCount uint) bool { + ret, _, _ := procImageList_SetImageCount.Call( + uintptr(himl), + uintptr(uNewCount)) + + return ret != 0 +} + +func ImageList_Add(himl HIMAGELIST, hbmImage, hbmMask HBITMAP) int { + ret, _, _ := procImageList_Add.Call( + uintptr(himl), + uintptr(hbmImage), + uintptr(hbmMask)) + + return int(ret) +} + +func ImageList_ReplaceIcon(himl HIMAGELIST, i int, hicon HICON) int { + ret, _, _ := procImageList_ReplaceIcon.Call( + uintptr(himl), + uintptr(i), + uintptr(hicon)) + + return int(ret) +} + +func ImageList_AddIcon(himl HIMAGELIST, hicon HICON) int { + return ImageList_ReplaceIcon(himl, -1, hicon) +} + +func ImageList_Remove(himl HIMAGELIST, i int) bool { + ret, _, _ := procImageList_Remove.Call( + uintptr(himl), + uintptr(i)) + + return ret != 0 +} + +func ImageList_RemoveAll(himl HIMAGELIST) bool { + return ImageList_Remove(himl, -1) +} + +func TrackMouseEvent(tme *TRACKMOUSEEVENT) bool { + ret, _, _ := procTrackMouseEvent.Call( + uintptr(unsafe.Pointer(tme))) + + return ret != 0 +} diff --git a/v3/pkg/w32/comdlg32.go b/v3/pkg/w32/comdlg32.go new file mode 100644 index 000000000..d28922c33 --- /dev/null +++ b/v3/pkg/w32/comdlg32.go @@ -0,0 +1,40 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modcomdlg32 = syscall.NewLazyDLL("comdlg32.dll") + + procGetSaveFileName = modcomdlg32.NewProc("GetSaveFileNameW") + procGetOpenFileName = modcomdlg32.NewProc("GetOpenFileNameW") + procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError") +) + +func GetOpenFileName(ofn *OPENFILENAME) bool { + ret, _, _ := procGetOpenFileName.Call( + uintptr(unsafe.Pointer(ofn))) + + return ret != 0 +} + +func GetSaveFileName(ofn *OPENFILENAME) bool { + ret, _, _ := procGetSaveFileName.Call( + uintptr(unsafe.Pointer(ofn))) + + return ret != 0 +} + +func CommDlgExtendedError() uint { + ret, _, _ := procCommDlgExtendedError.Call() + + return uint(ret) +} diff --git a/v3/pkg/w32/constants.go b/v3/pkg/w32/constants.go new file mode 100644 index 000000000..83ed4b9d1 --- /dev/null +++ b/v3/pkg/w32/constants.go @@ -0,0 +1,3713 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +const ( + FALSE = 0 + TRUE = 1 +) + +const ( + NO_ERROR = 0 + ERROR_SUCCESS = 0 + ERROR_FILE_NOT_FOUND = 2 + ERROR_PATH_NOT_FOUND = 3 + ERROR_ACCESS_DENIED = 5 + ERROR_INVALID_HANDLE = 6 + ERROR_BAD_FORMAT = 11 + ERROR_INVALID_NAME = 123 + ERROR_MORE_DATA = 234 + ERROR_NO_MORE_ITEMS = 259 + ERROR_INVALID_SERVICE_CONTROL = 1052 + ERROR_SERVICE_REQUEST_TIMEOUT = 1053 + ERROR_SERVICE_NO_THREAD = 1054 + ERROR_SERVICE_DATABASE_LOCKED = 1055 + ERROR_SERVICE_ALREADY_RUNNING = 1056 + ERROR_SERVICE_DISABLED = 1058 + ERROR_SERVICE_DOES_NOT_EXIST = 1060 + ERROR_SERVICE_CANNOT_ACCEPT_CTRL = 1061 + ERROR_SERVICE_NOT_ACTIVE = 1062 + ERROR_DATABASE_DOES_NOT_EXIST = 1065 + ERROR_SERVICE_DEPENDENCY_FAIL = 1068 + ERROR_SERVICE_LOGON_FAILED = 1069 + ERROR_SERVICE_MARKED_FOR_DELETE = 1072 + ERROR_SERVICE_DEPENDENCY_DELETED = 1075 +) + +const ( + SE_ERR_FNF = 2 + SE_ERR_PNF = 3 + SE_ERR_ACCESSDENIED = 5 + SE_ERR_OOM = 8 + SE_ERR_DLLNOTFOUND = 32 + SE_ERR_SHARE = 26 + SE_ERR_ASSOCINCOMPLETE = 27 + SE_ERR_DDETIMEOUT = 28 + SE_ERR_DDEFAIL = 29 + SE_ERR_DDEBUSY = 30 + SE_ERR_NOASSOC = 31 +) + +const ( + EDS_ROTATEDMODE = 0x00000001 + EDS_RAWMODE = 0x00000002 + DMDO_DEFAULT = 0 + DMDO_90 = 1 + DMDO_180 = 2 + DMDO_270 = 3 +) + +const ( + CW_USEDEFAULT = ^0x7fffffff +) + +const ( + IMAGE_BITMAP = 0 + IMAGE_ICON = 1 + IMAGE_CURSOR = 2 + IMAGE_ENHMETAFILE = 3 +) + +// SetProcessDpiAwareness constants +const ( + PROCESS_DPI_UNAWARE = 0 + PROCESS_SYSTEM_DPI_AWARE = 1 + PROCESS_PER_MONITOR_DPI_AWARE = 2 +) + +// SetProcessDpiAwarenessContext constants +// Credit: https://github.com/ncruces/zenity +const ( + DPI_AWARENESS_CONTEXT_UNAWARE = ^uintptr(1) + 1 + DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = ^uintptr(2) + 1 + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ^uintptr(3) + 1 + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ^uintptr(4) + 1 + DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = ^uintptr(5) + 1 +) + +// ShowWindow constants +const ( + SW_HIDE = 0 + SW_NORMAL = 1 + SW_SHOWNORMAL = 1 + SW_SHOWMINIMIZED = 2 + SW_MAXIMIZE = 3 + SW_SHOWMAXIMIZED = 3 + SW_SHOWNOACTIVATE = 4 + SW_SHOW = 5 + SW_MINIMIZE = 6 + SW_SHOWMINNOACTIVE = 7 + SW_SHOWNA = 8 + SW_RESTORE = 9 + SW_SHOWDEFAULT = 10 + SW_FORCEMINIMIZE = 11 +) + +// Window class styles +const ( + CS_VREDRAW = 0x00000001 + CS_HREDRAW = 0x00000002 + CS_KEYCVTWINDOW = 0x00000004 + CS_DBLCLKS = 0x00000008 + CS_OWNDC = 0x00000020 + CS_CLASSDC = 0x00000040 + CS_PARENTDC = 0x00000080 + CS_NOKEYCVT = 0x00000100 + CS_NOCLOSE = 0x00000200 + CS_SAVEBITS = 0x00000800 + CS_BYTEALIGNCLIENT = 0x00001000 + CS_BYTEALIGNWINDOW = 0x00002000 + CS_GLOBALCLASS = 0x00004000 + CS_IME = 0x00010000 + CS_DROPSHADOW = 0x00020000 +) + +// Predefined cursor constants +const ( + IDC_ARROW = 32512 + IDC_IBEAM = 32513 + IDC_WAIT = 32514 + IDC_CROSS = 32515 + IDC_UPARROW = 32516 + IDC_SIZENWSE = 32642 + IDC_SIZENESW = 32643 + IDC_SIZEWE = 32644 + IDC_SIZENS = 32645 + IDC_SIZEALL = 32646 + IDC_NO = 32648 + IDC_HAND = 32649 + IDC_APPSTARTING = 32650 + IDC_HELP = 32651 + IDC_ICON = 32641 + IDC_SIZE = 32640 +) + +// Predefined icon constants +const ( + IDI_APPLICATION = 32512 + IDI_HAND = 32513 + IDI_QUESTION = 32514 + IDI_EXCLAMATION = 32515 + IDI_ASTERISK = 32516 + IDI_WINLOGO = 32517 + IDI_WARNING = IDI_EXCLAMATION + IDI_ERROR = IDI_HAND + IDI_INFORMATION = IDI_ASTERISK +) + +const ( + RT_CURSOR = 1 // win32.MAKEINTRESOURCE(1) + RT_BITMAP = 2 + RT_ICON = 3 + RT_MENU = 4 + RT_DIALOG = 5 + RT_STRING = 6 + RT_FONTDIR = 7 + RT_FONT = 8 + RT_ACCELERATOR = 9 + RT_RCDATA = 10 + RT_MESSAGETABLE = 11 + RT_GROUP_CURSOR = 12 + RT_GROUP_ICON = 14 + RT_VERSION = 16 + RT_DLGINCLUDE = 17 + RT_PLUGPLAY = 19 + RT_VXD = 20 + RT_ANICURSOR = 21 + RT_ANIICON = 22 + RT_HTML = 23 + RT_MANIFEST = 24 +) + +// Button style constants +const ( + BS_3STATE = 5 + BS_AUTO3STATE = 6 + BS_AUTOCHECKBOX = 3 + BS_AUTORADIOBUTTON = 9 + BS_BITMAP = 128 + BS_BOTTOM = 0x800 + BS_CENTER = 0x300 + BS_CHECKBOX = 2 + BS_DEFPUSHBUTTON = 1 + BS_GROUPBOX = 7 + BS_ICON = 64 + BS_LEFT = 256 + BS_LEFTTEXT = 32 + BS_MULTILINE = 0x2000 + BS_NOTIFY = 0x4000 + BS_OWNERDRAW = 0xB + BS_PUSHBUTTON = 0 + BS_PUSHLIKE = 4096 + BS_RADIOBUTTON = 4 + BS_RIGHT = 512 + BS_RIGHTBUTTON = 32 + BS_TEXT = 0 + BS_TOP = 0x400 + BS_USERBUTTON = 8 + BS_VCENTER = 0xC00 + BS_FLAT = 0x8000 + BS_SPLITBUTTON = 0x000C // >= Vista + BS_DEFSPLITBUTTON = 0x000D // >= Vista +) + +// Button state constants +const ( + BST_CHECKED = 1 + BST_INDETERMINATE = 2 + BST_UNCHECKED = 0 + BST_FOCUS = 8 + BST_PUSHED = 4 +) + +// Predefined brushes constants +const ( + COLOR_3DDKSHADOW = 21 + COLOR_3DFACE = 15 + COLOR_3DHILIGHT = 20 + COLOR_3DHIGHLIGHT = 20 + COLOR_3DLIGHT = 22 + COLOR_BTNHILIGHT = 20 + COLOR_3DSHADOW = 16 + COLOR_ACTIVEBORDER = 10 + COLOR_ACTIVECAPTION = 2 + COLOR_APPWORKSPACE = 12 + COLOR_BACKGROUND = 1 + COLOR_DESKTOP = 1 + COLOR_BTNFACE = 15 + COLOR_BTNHIGHLIGHT = 20 + COLOR_BTNSHADOW = 16 + COLOR_BTNTEXT = 18 + COLOR_CAPTIONTEXT = 9 + COLOR_GRAYTEXT = 17 + COLOR_HIGHLIGHT = 13 + COLOR_HIGHLIGHTTEXT = 14 + COLOR_INACTIVEBORDER = 11 + COLOR_INACTIVECAPTION = 3 + COLOR_INACTIVECAPTIONTEXT = 19 + COLOR_INFOBK = 24 + COLOR_INFOTEXT = 23 + COLOR_MENU = 4 + COLOR_MENUTEXT = 7 + COLOR_SCROLLBAR = 0 + COLOR_WINDOW = 5 + COLOR_WINDOWFRAME = 6 + COLOR_WINDOWTEXT = 8 + COLOR_HOTLIGHT = 26 + COLOR_GRADIENTACTIVECAPTION = 27 + COLOR_GRADIENTINACTIVECAPTION = 28 +) + +// Button message constants +const ( + BM_CLICK = 245 + BM_GETCHECK = 240 + BM_GETIMAGE = 246 + BM_GETSTATE = 242 + BM_SETCHECK = 241 + BM_SETIMAGE = 247 + BM_SETSTATE = 243 + BM_SETSTYLE = 244 +) + +// Button notifications +const ( + BN_CLICKED = 0 + BN_PAINT = 1 + BN_HILITE = 2 + BN_PUSHED = BN_HILITE + BN_UNHILITE = 3 + BN_UNPUSHED = BN_UNHILITE + BN_DISABLE = 4 + BN_DOUBLECLICKED = 5 + BN_DBLCLK = BN_DOUBLECLICKED + BN_SETFOCUS = 6 + BN_KILLFOCUS = 7 +) + +// TrackPopupMenu[Ex] flags +const ( + TPM_CENTERALIGN = 0x0004 + TPM_LEFTALIGN = 0x0000 + TPM_RIGHTALIGN = 0x0008 + TPM_BOTTOMALIGN = 0x0020 + TPM_TOPALIGN = 0x0000 + TPM_VCENTERALIGN = 0x0010 + TPM_NONOTIFY = 0x0080 + TPM_RETURNCMD = 0x0100 + TPM_LEFTBUTTON = 0x0000 + TPM_RIGHTBUTTON = 0x0002 + TPM_HORNEGANIMATION = 0x0800 + TPM_HORPOSANIMATION = 0x0400 + TPM_NOANIMATION = 0x4000 + TPM_VERNEGANIMATION = 0x2000 + TPM_VERPOSANIMATION = 0x1000 + TPM_HORIZONTAL = 0x0000 + TPM_VERTICAL = 0x0040 +) + +// GetWindowLong and GetWindowLongPtr constants +const ( + GWL_EXSTYLE = -20 + GWL_STYLE = -16 + GWL_WNDPROC = -4 + GWLP_WNDPROC = -4 + GWL_HINSTANCE = -6 + GWLP_HINSTANCE = -6 + GWL_HWNDPARENT = -8 + GWLP_HWNDPARENT = -8 + GWL_ID = -12 + GWLP_ID = -12 + GWL_USERDATA = -21 + GWLP_USERDATA = -21 +) + +const ( + GW_HWNDFIRST = 0 + GW_HWNDLAST = 1 + GW_HWNDNEXT = 2 + GW_HWNDPREV = 3 + GW_OWNER = 4 + GW_CHILD = 5 + GW_ENABLEDPOPUP = 6 +) + +// Window style constants +const ( + WS_OVERLAPPED = 0x00000000 + WS_POPUP = 0x80000000 + WS_CHILD = 0x40000000 + WS_MINIMIZE = 0x20000000 + WS_VISIBLE = 0x10000000 + WS_DISABLED = 0x08000000 + WS_CLIPSIBLINGS = 0x04000000 + WS_CLIPCHILDREN = 0x02000000 + WS_MAXIMIZE = 0x01000000 + WS_CAPTION = 0x00C00000 + WS_BORDER = 0x00800000 + WS_DLGFRAME = 0x00400000 + WS_VSCROLL = 0x00200000 + WS_HSCROLL = 0x00100000 + WS_SYSMENU = 0x00080000 + WS_THICKFRAME = 0x00040000 + WS_GROUP = 0x00020000 + WS_TABSTOP = 0x00010000 + WS_MINIMIZEBOX = 0x00020000 + WS_MAXIMIZEBOX = 0x00010000 + WS_TILED = 0x00000000 + WS_ICONIC = 0x20000000 + WS_SIZEBOX = 0x00040000 + WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000 + WS_POPUPWINDOW = 0x80000000 | 0x00800000 | 0x00080000 + WS_CHILDWINDOW = 0x40000000 +) + +// Extended window style constants +const ( + WS_EX_DLGMODALFRAME = 0x00000001 + WS_EX_NOPARENTNOTIFY = 0x00000004 + WS_EX_TOPMOST = 0x00000008 + WS_EX_ACCEPTFILES = 0x00000010 + WS_EX_TRANSPARENT = 0x00000020 + WS_EX_MDICHILD = 0x00000040 + WS_EX_TOOLWINDOW = 0x00000080 + WS_EX_WINDOWEDGE = 0x00000100 + WS_EX_CLIENTEDGE = 0x00000200 + WS_EX_CONTEXTHELP = 0x00000400 + WS_EX_COMPOSITED = 0x02000000 + WS_EX_RIGHT = 0x00001000 + WS_EX_LEFT = 0x00000000 + WS_EX_RTLREADING = 0x00002000 + WS_EX_LTRREADING = 0x00000000 + WS_EX_LEFTSCROLLBAR = 0x00004000 + WS_EX_RIGHTSCROLLBAR = 0x00000000 + WS_EX_CONTROLPARENT = 0x00010000 + WS_EX_STATICEDGE = 0x00020000 + WS_EX_APPWINDOW = 0x00040000 + WS_EX_OVERLAPPEDWINDOW = 0x00000100 | 0x00000200 + WS_EX_PALETTEWINDOW = 0x00000100 | 0x00000080 | 0x00000008 + WS_EX_LAYERED = 0x00080000 + WS_EX_NOINHERITLAYOUT = 0x00100000 + WS_EX_NOREDIRECTIONBITMAP = 0x00200000 + WS_EX_LAYOUTRTL = 0x00400000 + WS_EX_NOACTIVATE = 0x08000000 +) + +// Window message constants +const ( + WM_APP = 32768 + WM_ACTIVATE = 6 + WM_ACTIVATEAPP = 28 + WM_AFXFIRST = 864 + WM_AFXLAST = 895 + WM_ASKCBFORMATNAME = 780 + WM_CANCELJOURNAL = 75 + WM_CANCELMODE = 31 + WM_CAPTURECHANGED = 533 + WM_CHANGECBCHAIN = 781 + WM_CHAR = 258 + WM_CHARTOITEM = 47 + WM_CHILDACTIVATE = 34 + WM_CLEAR = 771 + WM_CLOSE = 16 + WM_COMMAND = 273 + WM_COMMNOTIFY = 68 /* OBSOLETE */ + WM_COMPACTING = 65 + WM_COMPAREITEM = 57 + WM_CONTEXTMENU = 123 + WM_COPY = 769 + WM_COPYDATA = 74 + WM_CREATE = 1 + WM_CTLCOLORBTN = 309 + WM_CTLCOLORDLG = 310 + WM_CTLCOLOREDIT = 307 + WM_CTLCOLORLISTBOX = 308 + WM_CTLCOLORMSGBOX = 306 + WM_CTLCOLORSCROLLBAR = 311 + WM_CTLCOLORSTATIC = 312 + WM_CUT = 768 + WM_DEADCHAR = 259 + WM_DELETEITEM = 45 + WM_DESTROY = 2 + WM_DESTROYCLIPBOARD = 775 + WM_DEVICECHANGE = 537 + WM_DEVMODECHANGE = 27 + WM_DISPLAYCHANGE = 126 + WM_DRAWCLIPBOARD = 776 + WM_DRAWITEM = 43 + WM_DROPFILES = 563 + WM_ENABLE = 10 + WM_ENDSESSION = 22 + WM_ENTERIDLE = 289 + WM_ENTERMENULOOP = 529 + WM_ENTERSIZEMOVE = 561 + WM_ERASEBKGND = 20 + WM_EXITMENULOOP = 530 + WM_EXITSIZEMOVE = 562 + WM_FONTCHANGE = 29 + WM_GETDLGCODE = 135 + WM_GETFONT = 49 + WM_GETHOTKEY = 51 + WM_GETICON = 127 + WM_GETMINMAXINFO = 36 + WM_GETTEXT = 13 + WM_GETTEXTLENGTH = 14 + WM_HANDHELDFIRST = 856 + WM_HANDHELDLAST = 863 + WM_HELP = 83 + WM_HOTKEY = 786 + WM_HSCROLL = 276 + WM_HSCROLLCLIPBOARD = 782 + WM_ICONERASEBKGND = 39 + WM_INITDIALOG = 272 + WM_INITMENU = 278 + WM_INITMENUPOPUP = 279 + WM_INPUT = 0x00FF + WM_INPUTLANGCHANGE = 81 + WM_INPUTLANGCHANGEREQUEST = 80 + WM_KEYDOWN = 256 + WM_KEYUP = 257 + WM_KILLFOCUS = 8 + WM_MDIACTIVATE = 546 + WM_MDICASCADE = 551 + WM_MDICREATE = 544 + WM_MDIDESTROY = 545 + WM_MDIGETACTIVE = 553 + WM_MDIICONARRANGE = 552 + WM_MDIMAXIMIZE = 549 + WM_MDINEXT = 548 + WM_MDIREFRESHMENU = 564 + WM_MDIRESTORE = 547 + WM_MDISETMENU = 560 + WM_MDITILE = 550 + WM_MEASUREITEM = 44 + WM_GETOBJECT = 0x003D + WM_CHANGEUISTATE = 0x0127 + WM_UPDATEUISTATE = 0x0128 + WM_QUERYUISTATE = 0x0129 + WM_UNINITMENUPOPUP = 0x0125 + WM_MENURBUTTONUP = 290 + WM_MENUCOMMAND = 0x0126 + WM_MENUGETOBJECT = 0x0124 + WM_MENUDRAG = 0x0123 + WM_APPCOMMAND = 0x0319 + WM_MENUCHAR = 288 + WM_MENUSELECT = 287 + WM_MOVE = 3 + WM_MOVING = 534 + WM_NCACTIVATE = 134 + WM_NCCALCSIZE = 131 + WM_NCCREATE = 129 + WM_NCDESTROY = 130 + WM_NCHITTEST = 132 + WM_NCLBUTTONDBLCLK = 163 + WM_NCLBUTTONDOWN = 161 + WM_NCLBUTTONUP = 162 + WM_NCMBUTTONDBLCLK = 169 + WM_NCMBUTTONDOWN = 167 + WM_NCMBUTTONUP = 168 + WM_NCXBUTTONDOWN = 171 + WM_NCXBUTTONUP = 172 + WM_NCXBUTTONDBLCLK = 173 + WM_NCMOUSEHOVER = 0x02A0 + WM_NCMOUSELEAVE = 0x02A2 + WM_NCMOUSEMOVE = 160 + WM_NCPAINT = 133 + WM_NCRBUTTONDBLCLK = 166 + WM_NCRBUTTONDOWN = 164 + WM_NCRBUTTONUP = 165 + WM_NEXTDLGCTL = 40 + WM_NEXTMENU = 531 + WM_NOTIFY = 78 + WM_NOTIFYFORMAT = 85 + WM_NULL = 0 + WM_PAINT = 15 + WM_PAINTCLIPBOARD = 777 + WM_PAINTICON = 38 + WM_UAHDRAWMENU = 0x0091 + WM_UAHDRAWMENUITEM = 0x0092 + WM_UAHMEASUREMENUITEM = 0x0094 + WM_PALETTECHANGED = 785 + WM_PALETTEISCHANGING = 784 + WM_PARENTNOTIFY = 528 + WM_PASTE = 770 + WM_PENWINFIRST = 896 + WM_PENWINLAST = 911 + WM_POWER = 72 + WM_POWERBROADCAST = 536 + WM_PRINT = 791 + WM_PRINTCLIENT = 792 + WM_QUERYDRAGICON = 55 + WM_QUERYENDSESSION = 17 + WM_QUERYNEWPALETTE = 783 + WM_QUERYOPEN = 19 + WM_QUEUESYNC = 35 + WM_QUIT = 18 + WM_RENDERALLFORMATS = 774 + WM_RENDERFORMAT = 773 + WM_SETCURSOR = 32 + WM_SETFOCUS = 7 + WM_SETFONT = 48 + WM_SETHOTKEY = 50 + WM_SETICON = 128 + WM_SETREDRAW = 11 + WM_SETTEXT = 12 + WM_SETTINGCHANGE = 26 + WM_SHOWWINDOW = 24 + WM_SIZE = 5 + WM_SIZECLIPBOARD = 779 + WM_SIZING = 532 + WM_SPOOLERSTATUS = 42 + WM_STYLECHANGED = 125 + WM_STYLECHANGING = 124 + WM_SYSCHAR = 262 + WM_SYSCOLORCHANGE = 21 + WM_SYSCOMMAND = 274 + WM_SYSDEADCHAR = 263 + WM_SYSKEYDOWN = 260 + WM_SYSKEYUP = 261 + WM_TCARD = 82 + WM_THEMECHANGED = 794 + WM_TIMECHANGE = 30 + WM_TIMER = 275 + WM_UNDO = 772 + WM_USER = 1024 + WM_USERCHANGED = 84 + WM_VKEYTOITEM = 46 + WM_VSCROLL = 277 + WM_VSCROLLCLIPBOARD = 778 + WM_WINDOWPOSCHANGED = 71 + WM_WINDOWPOSCHANGING = 70 + WM_SELECTALL = 0x00B1 + WM_WININICHANGE = 26 + WM_KEYFIRST = 256 + WM_KEYLAST = 264 + WM_SYNCPAINT = 136 + WM_MOUSEACTIVATE = 33 + WM_MOUSEMOVE = 512 + WM_LBUTTONDOWN = 513 + WM_LBUTTONUP = 514 + WM_LBUTTONDBLCLK = 515 + WM_RBUTTONDOWN = 516 + WM_RBUTTONUP = 517 + WM_RBUTTONDBLCLK = 518 + WM_MBUTTONDOWN = 519 + WM_MBUTTONUP = 520 + WM_MBUTTONDBLCLK = 521 + WM_MOUSEWHEEL = 522 + WM_MOUSEHWHEEL = 526 + WM_MOUSEFIRST = 512 + WM_XBUTTONDOWN = 523 + WM_XBUTTONUP = 524 + WM_XBUTTONDBLCLK = 525 + WM_MOUSELAST = 525 + WM_MOUSEHOVER = 0x2A1 + WM_MOUSELEAVE = 0x2A3 + WM_CLIPBOARDUPDATE = 0x031D + WM_DPICHANGED = 0x02E0 +) + +const ( + SC_SIZE = 0xF000 // Resize the window + SC_MOVE = 0xF010 // Move the window + SC_MINIMIZE = 0xF020 // Minimize the window + SC_MAXIMIZE = 0xF030 // Maximize the window + SC_NEXTWINDOW = 0xF040 // Move to next window + SC_PREVWINDOW = 0xF050 // Move to previous window + SC_CLOSE = 0xF060 // Close the window + SC_VSCROLL = 0xF070 // Vertical scroll + SC_HSCROLL = 0xF080 // Horizontal scroll + SC_MOUSEMENU = 0xF090 // Mouse menu + SC_KEYMENU = 0xF100 // Key menu (triggered by Alt or F10) + SC_ARRANGE = 0xF110 // Arrange windows + SC_RESTORE = 0xF120 // Restore window from minimized/maximized + SC_TASKLIST = 0xF130 // Task list + SC_SCREENSAVE = 0xF140 // Screen saver + SC_HOTKEY = 0xF150 // Hotkey + SC_DEFAULT = 0xF160 // Default command + SC_MONITORPOWER = 0xF170 // Monitor power + SC_CONTEXTHELP = 0xF180 // Context help + SC_SEPARATOR = 0xF00F // Separator +) + +const ( + // Remove the Close option from the window menu + SC_MASK_CLOSE = ^uint16(SC_CLOSE) + // Mask for extracting the system command + SC_MASK_CMD = 0xFFF0 +) + +// WM_ACTIVATE +const ( + WA_INACTIVE = 0 + WA_ACTIVE = 1 + WA_CLICKACTIVE = 2 +) + +const LF_FACESIZE = 32 + +// Font weight constants +const ( + FW_DONTCARE = 0 + FW_THIN = 100 + FW_EXTRALIGHT = 200 + FW_ULTRALIGHT = FW_EXTRALIGHT + FW_LIGHT = 300 + FW_NORMAL = 400 + FW_REGULAR = 400 + FW_MEDIUM = 500 + FW_SEMIBOLD = 600 + FW_DEMIBOLD = FW_SEMIBOLD + FW_BOLD = 700 + FW_EXTRABOLD = 800 + FW_ULTRABOLD = FW_EXTRABOLD + FW_HEAVY = 900 + FW_BLACK = FW_HEAVY +) + +// Charset constants +const ( + ANSI_CHARSET = 0 + DEFAULT_CHARSET = 1 + SYMBOL_CHARSET = 2 + SHIFTJIS_CHARSET = 128 + HANGEUL_CHARSET = 129 + HANGUL_CHARSET = 129 + GB2312_CHARSET = 134 + CHINESEBIG5_CHARSET = 136 + GREEK_CHARSET = 161 + TURKISH_CHARSET = 162 + HEBREW_CHARSET = 177 + ARABIC_CHARSET = 178 + BALTIC_CHARSET = 186 + RUSSIAN_CHARSET = 204 + THAI_CHARSET = 222 + EASTEUROPE_CHARSET = 238 + OEM_CHARSET = 255 + JOHAB_CHARSET = 130 + VIETNAMESE_CHARSET = 163 + MAC_CHARSET = 77 +) + +const ( + // PBT_APMPOWERSTATUSCHANGE - Power status has changed. + PBT_APMPOWERSTATUSCHANGE = 10 + + // PBT_APMRESUMEAUTOMATIC -Operation is resuming automatically from a low-power state. This message is sent every time the system resumes. + PBT_APMRESUMEAUTOMATIC = 18 + + // PBT_APMRESUMESUSPEND - Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key. + PBT_APMRESUMESUSPEND = 7 + + // PBT_APMSUSPEND - System is suspending operation. + PBT_APMSUSPEND = 4 + + // PBT_POWERSETTINGCHANGE - A power setting change event has been received. + PBT_POWERSETTINGCHANGE = 32787 +) + +// Font output precision constants +const ( + OUT_DEFAULT_PRECIS = 0 + OUT_STRING_PRECIS = 1 + OUT_CHARACTER_PRECIS = 2 + OUT_STROKE_PRECIS = 3 + OUT_TT_PRECIS = 4 + OUT_DEVICE_PRECIS = 5 + OUT_RASTER_PRECIS = 6 + OUT_TT_ONLY_PRECIS = 7 + OUT_OUTLINE_PRECIS = 8 + OUT_PS_ONLY_PRECIS = 10 +) + +// Font clipping precision constants +const ( + CLIP_DEFAULT_PRECIS = 0 + CLIP_CHARACTER_PRECIS = 1 + CLIP_STROKE_PRECIS = 2 + CLIP_MASK = 15 + CLIP_LH_ANGLES = 16 + CLIP_TT_ALWAYS = 32 + CLIP_EMBEDDED = 128 +) + +// Font output quality constants +const ( + DEFAULT_QUALITY = 0 + DRAFT_QUALITY = 1 + PROOF_QUALITY = 2 + NONANTIALIASED_QUALITY = 3 + ANTIALIASED_QUALITY = 4 + CLEARTYPE_QUALITY = 5 +) + +// Font pitch constants +const ( + DEFAULT_PITCH = 0 + FIXED_PITCH = 1 + VARIABLE_PITCH = 2 +) + +// Font family constants +const ( + FF_DECORATIVE = 80 + FF_DONTCARE = 0 + FF_MODERN = 48 + FF_ROMAN = 16 + FF_SCRIPT = 64 + FF_SWISS = 32 +) + +// DeviceCapabilities capabilities +const ( + DC_FIELDS = 1 + DC_PAPERS = 2 + DC_PAPERSIZE = 3 + DC_MINEXTENT = 4 + DC_MAXEXTENT = 5 + DC_BINS = 6 + DC_DUPLEX = 7 + DC_SIZE = 8 + DC_EXTRA = 9 + DC_VERSION = 10 + DC_DRIVER = 11 + DC_BINNAMES = 12 + DC_ENUMRESOLUTIONS = 13 + DC_FILEDEPENDENCIES = 14 + DC_TRUETYPE = 15 + DC_PAPERNAMES = 16 + DC_ORIENTATION = 17 + DC_COPIES = 18 + DC_BINADJUST = 19 + DC_EMF_COMPLIANT = 20 + DC_DATATYPE_PRODUCED = 21 + DC_COLLATE = 22 + DC_MANUFACTURER = 23 + DC_MODEL = 24 + DC_PERSONALITY = 25 + DC_PRINTRATE = 26 + DC_PRINTRATEUNIT = 27 + DC_PRINTERMEM = 28 + DC_MEDIAREADY = 29 + DC_STAPLE = 30 + DC_PRINTRATEPPM = 31 + DC_COLORDEVICE = 32 + DC_NUP = 33 + DC_MEDIATYPENAMES = 34 + DC_MEDIATYPES = 35 +) + +// GetDeviceCaps index constants +const ( + DRIVERVERSION = 0 + TECHNOLOGY = 2 + HORZSIZE = 4 + VERTSIZE = 6 + HORZRES = 8 + VERTRES = 10 + LOGPIXELSX = 88 + LOGPIXELSY = 90 + BITSPIXEL = 12 + PLANES = 14 + NUMBRUSHES = 16 + NUMPENS = 18 + NUMFONTS = 22 + NUMCOLORS = 24 + NUMMARKERS = 20 + ASPECTX = 40 + ASPECTY = 42 + ASPECTXY = 44 + PDEVICESIZE = 26 + CLIPCAPS = 36 + SIZEPALETTE = 104 + NUMRESERVED = 106 + COLORRES = 108 + PHYSICALWIDTH = 110 + PHYSICALHEIGHT = 111 + PHYSICALOFFSETX = 112 + PHYSICALOFFSETY = 113 + SCALINGFACTORX = 114 + SCALINGFACTORY = 115 + VREFRESH = 116 + DESKTOPHORZRES = 118 + DESKTOPVERTRES = 117 + BLTALIGNMENT = 119 + SHADEBLENDCAPS = 120 + COLORMGMTCAPS = 121 + RASTERCAPS = 38 + CURVECAPS = 28 + LINECAPS = 30 + POLYGONALCAPS = 32 + TEXTCAPS = 34 +) + +// GetDeviceCaps TECHNOLOGY constants +const ( + DT_PLOTTER = 0 + DT_RASDISPLAY = 1 + DT_RASPRINTER = 2 + DT_RASCAMERA = 3 + DT_CHARSTREAM = 4 + DT_METAFILE = 5 + DT_DISPFILE = 6 +) + +// GetDeviceCaps SHADEBLENDCAPS constants +const ( + SB_NONE = 0x00 + SB_CONST_ALPHA = 0x01 + SB_PIXEL_ALPHA = 0x02 + SB_PREMULT_ALPHA = 0x04 + SB_GRAD_RECT = 0x10 + SB_GRAD_TRI = 0x20 +) + +// GetDeviceCaps COLORMGMTCAPS constants +const ( + CM_NONE = 0x00 + CM_DEVICE_ICM = 0x01 + CM_GAMMA_RAMP = 0x02 + CM_CMYK_COLOR = 0x04 +) + +// GetDeviceCaps RASTERCAPS constants +const ( + RC_BANDING = 2 + RC_BITBLT = 1 + RC_BITMAP64 = 8 + RC_DI_BITMAP = 128 + RC_DIBTODEV = 512 + RC_FLOODFILL = 4096 + RC_GDI20_OUTPUT = 16 + RC_PALETTE = 256 + RC_SCALING = 4 + RC_STRETCHBLT = 2048 + RC_STRETCHDIB = 8192 + RC_DEVBITS = 0x8000 + RC_OP_DX_OUTPUT = 0x4000 +) + +// GetDeviceCaps CURVECAPS constants +const ( + CC_NONE = 0 + CC_CIRCLES = 1 + CC_PIE = 2 + CC_CHORD = 4 + CC_ELLIPSES = 8 + CC_WIDE = 16 + CC_STYLED = 32 + CC_WIDESTYLED = 64 + CC_INTERIORS = 128 + CC_ROUNDRECT = 256 +) + +// GetDeviceCaps LINECAPS constants +const ( + LC_NONE = 0 + LC_POLYLINE = 2 + LC_MARKER = 4 + LC_POLYMARKER = 8 + LC_WIDE = 16 + LC_STYLED = 32 + LC_WIDESTYLED = 64 + LC_INTERIORS = 128 +) + +// GetDeviceCaps POLYGONALCAPS constants +const ( + PC_NONE = 0 + PC_POLYGON = 1 + PC_POLYPOLYGON = 256 + PC_PATHS = 512 + PC_RECTANGLE = 2 + PC_WINDPOLYGON = 4 + PC_SCANLINE = 8 + PC_TRAPEZOID = 4 + PC_WIDE = 16 + PC_STYLED = 32 + PC_WIDESTYLED = 64 + PC_INTERIORS = 128 +) + +// GetDeviceCaps TEXTCAPS constants +const ( + TC_OP_CHARACTER = 1 + TC_OP_STROKE = 2 + TC_CP_STROKE = 4 + TC_CR_90 = 8 + TC_CR_ANY = 16 + TC_SF_X_YINDEP = 32 + TC_SA_DOUBLE = 64 + TC_SA_INTEGER = 128 + TC_SA_CONTIN = 256 + TC_EA_DOUBLE = 512 + TC_IA_ABLE = 1024 + TC_UA_ABLE = 2048 + TC_SO_ABLE = 4096 + TC_RA_ABLE = 8192 + TC_VA_ABLE = 16384 + TC_RESERVED = 32768 + TC_SCROLLBLT = 65536 +) + +// Static control styles +const ( + SS_BITMAP = 14 + SS_BLACKFRAME = 7 + SS_BLACKRECT = 4 + SS_CENTER = 1 + SS_CENTERIMAGE = 512 + SS_EDITCONTROL = 0x2000 + SS_ENHMETAFILE = 15 + SS_ETCHEDFRAME = 18 + SS_ETCHEDHORZ = 16 + SS_ETCHEDVERT = 17 + SS_GRAYFRAME = 8 + SS_GRAYRECT = 5 + SS_ICON = 3 + SS_LEFT = 0 + SS_LEFTNOWORDWRAP = 0xc + SS_NOPREFIX = 128 + SS_NOTIFY = 256 + SS_OWNERDRAW = 0xd + SS_REALSIZECONTROL = 0x040 + SS_REALSIZEIMAGE = 0x800 + SS_RIGHT = 2 + SS_RIGHTJUST = 0x400 + SS_SIMPLE = 11 + SS_SUNKEN = 4096 + SS_WHITEFRAME = 9 + SS_WHITERECT = 6 + SS_USERITEM = 10 + SS_TYPEMASK = 0x0000001F + SS_ENDELLIPSIS = 0x00004000 + SS_PATHELLIPSIS = 0x00008000 + SS_WORDELLIPSIS = 0x0000C000 + SS_ELLIPSISMASK = 0x0000C000 +) + +const ( + FLASHW_STOP = 0 // Stop flashing. The system restores the window to its original state. + FLASHW_CAPTION = 1 // Flash the window caption. + FLASHW_TRAY = 2 // Flash the taskbar button. + FLASHW_ALL = 3 // Flash both the window caption and taskbar button. This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. + FLASHW_TIMER = 4 // Flash continuously, until the FLASHW_STOP flag is set. + FLASHW_TIMERNOFG = 12 // Flash continuously until the window comes to the foreground. +) + +// Edit styles +const ( + ES_LEFT = 0x0000 + ES_CENTER = 0x0001 + ES_RIGHT = 0x0002 + ES_MULTILINE = 0x0004 + ES_UPPERCASE = 0x0008 + ES_LOWERCASE = 0x0010 + ES_PASSWORD = 0x0020 + ES_AUTOVSCROLL = 0x0040 + ES_AUTOHSCROLL = 0x0080 + ES_NOHIDESEL = 0x0100 + ES_OEMCONVERT = 0x0400 + ES_READONLY = 0x0800 + ES_WANTRETURN = 0x1000 + ES_NUMBER = 0x2000 +) + +// Edit notifications +const ( + EN_SETFOCUS = 0x0100 + EN_KILLFOCUS = 0x0200 + EN_CHANGE = 0x0300 + EN_UPDATE = 0x0400 + EN_ERRSPACE = 0x0500 + EN_MAXTEXT = 0x0501 + EN_HSCROLL = 0x0601 + EN_VSCROLL = 0x0602 + EN_ALIGN_LTR_EC = 0x0700 + EN_ALIGN_RTL_EC = 0x0701 +) + +// Edit messages +const ( + EM_GETSEL = 0x00B0 + EM_SETSEL = 0x00B1 + EM_GETRECT = 0x00B2 + EM_SETRECT = 0x00B3 + EM_SETRECTNP = 0x00B4 + EM_SCROLL = 0x00B5 + EM_LINESCROLL = 0x00B6 + EM_SCROLLCARET = 0x00B7 + EM_GETMODIFY = 0x00B8 + EM_SETMODIFY = 0x00B9 + EM_GETLINECOUNT = 0x00BA + EM_LINEINDEX = 0x00BB + EM_SETHANDLE = 0x00BC + EM_GETHANDLE = 0x00BD + EM_GETTHUMB = 0x00BE + EM_LINELENGTH = 0x00C1 + EM_REPLACESEL = 0x00C2 + EM_GETLINE = 0x00C4 + EM_LIMITTEXT = 0x00C5 + EM_CANUNDO = 0x00C6 + EM_UNDO = 0x00C7 + EM_FMTLINES = 0x00C8 + EM_LINEFROMCHAR = 0x00C9 + EM_SETTABSTOPS = 0x00CB + EM_SETPASSWORDCHAR = 0x00CC + EM_EMPTYUNDOBUFFER = 0x00CD + EM_GETFIRSTVISIBLELINE = 0x00CE + EM_SETREADONLY = 0x00CF + EM_SETWORDBREAKPROC = 0x00D0 + EM_GETWORDBREAKPROC = 0x00D1 + EM_GETPASSWORDCHAR = 0x00D2 + EM_SETMARGINS = 0x00D3 + EM_GETMARGINS = 0x00D4 + EM_SETLIMITTEXT = EM_LIMITTEXT + EM_GETLIMITTEXT = 0x00D5 + EM_POSFROMCHAR = 0x00D6 + EM_CHARFROMPOS = 0x00D7 + EM_SETIMESTATUS = 0x00D8 + EM_GETIMESTATUS = 0x00D9 + EM_SETCUEBANNER = 0x1501 + EM_GETCUEBANNER = 0x1502 +) + +const ( + CCM_FIRST = 0x2000 + CCM_LAST = CCM_FIRST + 0x200 + CCM_SETBKCOLOR = 8193 + CCM_SETCOLORSCHEME = 8194 + CCM_GETCOLORSCHEME = 8195 + CCM_GETDROPTARGET = 8196 + CCM_SETUNICODEFORMAT = 8197 + CCM_GETUNICODEFORMAT = 8198 + CCM_SETVERSION = 0x2007 + CCM_GETVERSION = 0x2008 + CCM_SETNOTIFYWINDOW = 0x2009 + CCM_SETWINDOWTHEME = 0x200b + CCM_DPISCALE = 0x200c +) + +// Common controls styles +const ( + CCS_TOP = 1 + CCS_NOMOVEY = 2 + CCS_BOTTOM = 3 + CCS_NORESIZE = 4 + CCS_NOPARENTALIGN = 8 + CCS_ADJUSTABLE = 32 + CCS_NODIVIDER = 64 + CCS_VERT = 128 + CCS_LEFT = 129 + CCS_NOMOVEX = 130 + CCS_RIGHT = 131 +) + +// ProgressBar messages +const ( + PROGRESS_CLASS = "msctls_progress32" + PBM_SETPOS = WM_USER + 2 + PBM_DELTAPOS = WM_USER + 3 + PBM_SETSTEP = WM_USER + 4 + PBM_STEPIT = WM_USER + 5 + PBM_SETRANGE32 = 1030 + PBM_GETRANGE = 1031 + PBM_GETPOS = 1032 + PBM_SETBARCOLOR = 1033 + PBM_SETBKCOLOR = CCM_SETBKCOLOR + PBS_SMOOTH = 1 + PBS_VERTICAL = 4 +) + +// Trackbar messages and constants +const ( + TBS_AUTOTICKS = 1 + TBS_VERT = 2 + TBS_HORZ = 0 + TBS_TOP = 4 + TBS_BOTTOM = 0 + TBS_LEFT = 4 + TBS_RIGHT = 0 + TBS_BOTH = 8 + TBS_NOTICKS = 16 + TBS_ENABLESELRANGE = 32 + TBS_FIXEDLENGTH = 64 + TBS_NOTHUMB = 128 + TBS_TOOLTIPS = 0x0100 +) + +const ( + TBM_GETPOS = WM_USER + TBM_GETRANGEMIN = WM_USER + 1 + TBM_GETRANGEMAX = WM_USER + 2 + TBM_GETTIC = WM_USER + 3 + TBM_SETTIC = WM_USER + 4 + TBM_SETPOS = WM_USER + 5 + TBM_SETRANGE = WM_USER + 6 + TBM_SETRANGEMIN = WM_USER + 7 + TBM_SETRANGEMAX = WM_USER + 8 + TBM_CLEARTICS = WM_USER + 9 + TBM_SETSEL = WM_USER + 10 + TBM_SETSELSTART = WM_USER + 11 + TBM_SETSELEND = WM_USER + 12 + TBM_GETPTICS = WM_USER + 14 + TBM_GETTICPOS = WM_USER + 15 + TBM_GETNUMTICS = WM_USER + 16 + TBM_GETSELSTART = WM_USER + 17 + TBM_GETSELEND = WM_USER + 18 + TBM_CLEARSEL = WM_USER + 19 + TBM_SETTICFREQ = WM_USER + 20 + TBM_SETPAGESIZE = WM_USER + 21 + TBM_GETPAGESIZE = WM_USER + 22 + TBM_SETLINESIZE = WM_USER + 23 + TBM_GETLINESIZE = WM_USER + 24 + TBM_GETTHUMBRECT = WM_USER + 25 + TBM_GETCHANNELRECT = WM_USER + 26 + TBM_SETTHUMBLENGTH = WM_USER + 27 + TBM_GETTHUMBLENGTH = WM_USER + 28 + TBM_SETTOOLTIPS = WM_USER + 29 + TBM_GETTOOLTIPS = WM_USER + 30 + TBM_SETTIPSIDE = WM_USER + 31 + TBM_SETBUDDY = WM_USER + 32 + TBM_GETBUDDY = WM_USER + 33 +) + +const ( + TB_LINEUP = 0 + TB_LINEDOWN = 1 + TB_PAGEUP = 2 + TB_PAGEDOWN = 3 + TB_THUMBPOSITION = 4 + TB_THUMBTRACK = 5 + TB_TOP = 6 + TB_BOTTOM = 7 + TB_ENDTRACK = 8 +) + +// GetOpenFileName and GetSaveFileName extended flags +const ( + OFN_EX_NOPLACESBAR = 0x00000001 +) + +// GetOpenFileName and GetSaveFileName flags +const ( + OFN_ALLOWMULTISELECT = 0x00000200 + OFN_CREATEPROMPT = 0x00002000 + OFN_DONTADDTORECENT = 0x02000000 + OFN_ENABLEHOOK = 0x00000020 + OFN_ENABLEINCLUDENOTIFY = 0x00400000 + OFN_ENABLESIZING = 0x00800000 + OFN_ENABLETEMPLATE = 0x00000040 + OFN_ENABLETEMPLATEHANDLE = 0x00000080 + OFN_EXPLORER = 0x00080000 + OFN_EXTENSIONDIFFERENT = 0x00000400 + OFN_FILEMUSTEXIST = 0x00001000 + OFN_FORCESHOWHIDDEN = 0x10000000 + OFN_HIDEREADONLY = 0x00000004 + OFN_LONGNAMES = 0x00200000 + OFN_NOCHANGEDIR = 0x00000008 + OFN_NODEREFERENCELINKS = 0x00100000 + OFN_NOLONGNAMES = 0x00040000 + OFN_NONETWORKBUTTON = 0x00020000 + OFN_NOREADONLYRETURN = 0x00008000 + OFN_NOTESTFILECREATE = 0x00010000 + OFN_NOVALIDATE = 0x00000100 + OFN_OVERWRITEPROMPT = 0x00000002 + OFN_PATHMUSTEXIST = 0x00000800 + OFN_READONLY = 0x00000001 + OFN_SHAREAWARE = 0x00004000 + OFN_SHOWHELP = 0x00000010 +) + +// SHBrowseForFolder flags +const ( + BIF_RETURNONLYFSDIRS = 0x00000001 + BIF_DONTGOBELOWDOMAIN = 0x00000002 + BIF_STATUSTEXT = 0x00000004 + BIF_RETURNFSANCESTORS = 0x00000008 + BIF_EDITBOX = 0x00000010 + BIF_VALIDATE = 0x00000020 + BIF_NEWDIALOGSTYLE = 0x00000040 + BIF_BROWSEINCLUDEURLS = 0x00000080 + BIF_USENEWUI = BIF_EDITBOX | BIF_NEWDIALOGSTYLE + BIF_UAHINT = 0x00000100 + BIF_NONEWFOLDERBUTTON = 0x00000200 + BIF_NOTRANSLATETARGETS = 0x00000400 + BIF_BROWSEFORCOMPUTER = 0x00001000 + BIF_BROWSEFORPRINTER = 0x00002000 + BIF_BROWSEINCLUDEFILES = 0x00004000 + BIF_SHAREABLE = 0x00008000 + BIF_BROWSEFILEJUNCTIONS = 0x00010000 +) + +// MessageBox flags +const ( + MB_OK = 0x00000000 + MB_OKCANCEL = 0x00000001 + MB_ABORTRETRYIGNORE = 0x00000002 + MB_YESNOCANCEL = 0x00000003 + MB_YESNO = 0x00000004 + MB_RETRYCANCEL = 0x00000005 + MB_CANCELTRYCONTINUE = 0x00000006 + MB_ICONHAND = 0x00000010 + MB_ICONQUESTION = 0x00000020 + MB_ICONEXCLAMATION = 0x00000030 + MB_ICONASTERISK = 0x00000040 + MB_USERICON = 0x00000080 + MB_ICONWARNING = MB_ICONEXCLAMATION + MB_ICONERROR = MB_ICONHAND + MB_ICONINFORMATION = MB_ICONASTERISK + MB_ICONSTOP = MB_ICONHAND + MB_DEFBUTTON1 = 0x00000000 + MB_DEFBUTTON2 = 0x00000100 + MB_DEFBUTTON3 = 0x00000200 + MB_DEFBUTTON4 = 0x00000300 +) + +// COM +const ( + E_INVALIDARG = 0x80070057 + E_OUTOFMEMORY = 0x8007000E + E_UNEXPECTED = 0x8000FFFF +) + +const ( + S_OK = 0 + S_FALSE = 0x0001 + RPC_E_CHANGED_MODE = 0x80010106 +) + +// GetSystemMetrics constants +const ( + SM_CXSCREEN = 0 + SM_CYSCREEN = 1 + SM_CXVSCROLL = 2 + SM_CYHSCROLL = 3 + SM_CYCAPTION = 4 + SM_CXBORDER = 5 + SM_CYBORDER = 6 + SM_CXDLGFRAME = 7 + SM_CYDLGFRAME = 8 + SM_CYVTHUMB = 9 + SM_CXHTHUMB = 10 + SM_CXICON = 11 + SM_CYICON = 12 + SM_CXCURSOR = 13 + SM_CYCURSOR = 14 + SM_CYMENU = 15 + SM_CXFULLSCREEN = 16 + SM_CYFULLSCREEN = 17 + SM_CYKANJIWINDOW = 18 + SM_MOUSEPRESENT = 19 + SM_CYVSCROLL = 20 + SM_CXHSCROLL = 21 + SM_DEBUG = 22 + SM_SWAPBUTTON = 23 + SM_RESERVED1 = 24 + SM_RESERVED2 = 25 + SM_RESERVED3 = 26 + SM_RESERVED4 = 27 + SM_CXMIN = 28 + SM_CYMIN = 29 + SM_CXSIZE = 30 + SM_CYSIZE = 31 + SM_CXFRAME = 32 + SM_CYFRAME = 33 + SM_CXMINTRACK = 34 + SM_CYMINTRACK = 35 + SM_CXDOUBLECLK = 36 + SM_CYDOUBLECLK = 37 + SM_CXICONSPACING = 38 + SM_CYICONSPACING = 39 + SM_MENUDROPALIGNMENT = 40 + SM_PENWINDOWS = 41 + SM_DBCSENABLED = 42 + SM_CMOUSEBUTTONS = 43 + SM_CXFIXEDFRAME = SM_CXDLGFRAME + SM_CYFIXEDFRAME = SM_CYDLGFRAME + SM_CXSIZEFRAME = SM_CXFRAME + SM_CYSIZEFRAME = SM_CYFRAME + SM_SECURE = 44 + SM_CXEDGE = 45 + SM_CYEDGE = 46 + SM_CXMINSPACING = 47 + SM_CYMINSPACING = 48 + SM_CXSMICON = 49 + SM_CYSMICON = 50 + SM_CYSMCAPTION = 51 + SM_CXSMSIZE = 52 + SM_CYSMSIZE = 53 + SM_CXMENUSIZE = 54 + SM_CYMENUSIZE = 55 + SM_ARRANGE = 56 + SM_CXMINIMIZED = 57 + SM_CYMINIMIZED = 58 + SM_CXMAXTRACK = 59 + SM_CYMAXTRACK = 60 + SM_CXMAXIMIZED = 61 + SM_CYMAXIMIZED = 62 + SM_NETWORK = 63 + SM_CLEANBOOT = 67 + SM_CXDRAG = 68 + SM_CYDRAG = 69 + SM_SHOWSOUNDS = 70 + SM_CXMENUCHECK = 71 + SM_CYMENUCHECK = 72 + SM_SLOWMACHINE = 73 + SM_MIDEASTENABLED = 74 + SM_MOUSEWHEELPRESENT = 75 + SM_XVIRTUALSCREEN = 76 + SM_YVIRTUALSCREEN = 77 + SM_CXVIRTUALSCREEN = 78 + SM_CYVIRTUALSCREEN = 79 + SM_CMONITORS = 80 + SM_SAMEDISPLAYFORMAT = 81 + SM_IMMENABLED = 82 + SM_CXFOCUSBORDER = 83 + SM_CYFOCUSBORDER = 84 + SM_TABLETPC = 86 + SM_MEDIACENTER = 87 + SM_STARTER = 88 + SM_SERVERR2 = 89 + SM_CMETRICS = 91 + SM_CXPADDEDBORDER = 92 + SM_REMOTESESSION = 0x1000 + SM_SHUTTINGDOWN = 0x2000 + SM_REMOTECONTROL = 0x2001 + SM_CARETBLINKINGENABLED = 0x2002 +) + +const ( + CLSCTX_INPROC_SERVER = 1 + CLSCTX_INPROC_HANDLER = 2 + CLSCTX_LOCAL_SERVER = 4 + CLSCTX_INPROC_SERVER16 = 8 + CLSCTX_REMOTE_SERVER = 16 + CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER +) + +const ( + COINIT_APARTMENTTHREADED = 0x2 + COINIT_MULTITHREADED = 0x0 + COINIT_DISABLE_OLE1DDE = 0x4 + COINIT_SPEED_OVER_MEMORY = 0x8 +) + +const ( + DISPATCH_METHOD = 1 + DISPATCH_PROPERTYGET = 2 + DISPATCH_PROPERTYPUT = 4 + DISPATCH_PROPERTYPUTREF = 8 +) + +const ( + CC_FASTCALL = iota + CC_CDECL + CC_MSCPASCAL + CC_PASCAL = CC_MSCPASCAL + CC_MACPASCAL + CC_STDCALL + CC_FPFASTCALL + CC_SYSCALL + CC_MPWCDECL + CC_MPWPASCAL + CC_MAX = CC_MPWPASCAL +) + +const ( + VT_EMPTY = 0x0 + VT_NULL = 0x1 + VT_I2 = 0x2 + VT_I4 = 0x3 + VT_R4 = 0x4 + VT_R8 = 0x5 + VT_CY = 0x6 + VT_DATE = 0x7 + VT_BSTR = 0x8 + VT_DISPATCH = 0x9 + VT_ERROR = 0xa + VT_BOOL = 0xb + VT_VARIANT = 0xc + VT_UNKNOWN = 0xd + VT_DECIMAL = 0xe + VT_I1 = 0x10 + VT_UI1 = 0x11 + VT_UI2 = 0x12 + VT_UI4 = 0x13 + VT_I8 = 0x14 + VT_UI8 = 0x15 + VT_INT = 0x16 + VT_UINT = 0x17 + VT_VOID = 0x18 + VT_HRESULT = 0x19 + VT_PTR = 0x1a + VT_SAFEARRAY = 0x1b + VT_CARRAY = 0x1c + VT_USERDEFINED = 0x1d + VT_LPSTR = 0x1e + VT_LPWSTR = 0x1f + VT_RECORD = 0x24 + VT_INT_PTR = 0x25 + VT_UINT_PTR = 0x26 + VT_FILETIME = 0x40 + VT_BLOB = 0x41 + VT_STREAM = 0x42 + VT_STORAGE = 0x43 + VT_STREAMED_OBJECT = 0x44 + VT_STORED_OBJECT = 0x45 + VT_BLOB_OBJECT = 0x46 + VT_CF = 0x47 + VT_CLSID = 0x48 + VT_BSTR_BLOB = 0xfff + VT_VECTOR = 0x1000 + VT_ARRAY = 0x2000 + VT_BYREF = 0x4000 + VT_RESERVED = 0x8000 + VT_ILLEGAL = 0xffff + VT_ILLEGALMASKED = 0xfff + VT_TYPEMASK = 0xfff +) + +const ( + DISPID_UNKNOWN = -1 + DISPID_VALUE = 0 + DISPID_PROPERTYPUT = -3 + DISPID_NEWENUM = -4 + DISPID_EVALUATE = -5 + DISPID_CONSTRUCTOR = -6 + DISPID_DESTRUCTOR = -7 + DISPID_COLLECT = -8 +) + +const ( + MONITOR_DEFAULTTONULL = 0x00000000 + MONITOR_DEFAULTTOPRIMARY = 0x00000001 + MONITOR_DEFAULTTONEAREST = 0x00000002 + + MONITORINFOF_PRIMARY = 0x00000001 +) + +const ( + CCHDEVICENAME = 32 + CCHFORMNAME = 32 +) + +const ( + IDOK = 1 + IDCANCEL = 2 + IDABORT = 3 + IDRETRY = 4 + IDIGNORE = 5 + IDYES = 6 + IDNO = 7 + IDCLOSE = 8 + IDHELP = 9 + IDTRYAGAIN = 10 + IDCONTINUE = 11 + IDTIMEOUT = 32000 +) + +// Generic WM_NOTIFY notification codes +const ( + NM_FIRST = 0 + NM_OUTOFMEMORY = NM_FIRST - 1 + NM_CLICK = NM_FIRST - 2 + NM_DBLCLK = NM_FIRST - 3 + NM_RETURN = NM_FIRST - 4 + NM_RCLICK = NM_FIRST - 5 + NM_RDBLCLK = NM_FIRST - 6 + NM_SETFOCUS = NM_FIRST - 7 + NM_KILLFOCUS = NM_FIRST - 8 + NM_CUSTOMDRAW = NM_FIRST - 12 + NM_HOVER = NM_FIRST - 13 + NM_NCHITTEST = NM_FIRST - 14 + NM_KEYDOWN = NM_FIRST - 15 + NM_RELEASEDCAPTURE = NM_FIRST - 16 + NM_SETCURSOR = NM_FIRST - 17 + NM_CHAR = NM_FIRST - 18 + NM_TOOLTIPSCREATED = NM_FIRST - 19 + NM_LAST = NM_FIRST - 99 +) + +// ListView messages +const ( + LVM_FIRST = 0x1000 + LVM_GETITEMCOUNT = LVM_FIRST + 4 + LVM_SETIMAGELIST = LVM_FIRST + 3 + LVM_GETIMAGELIST = LVM_FIRST + 2 + LVM_GETITEM = LVM_FIRST + 75 + LVM_SETITEM = LVM_FIRST + 76 + LVM_INSERTITEM = LVM_FIRST + 77 + LVM_DELETEITEM = LVM_FIRST + 8 + LVM_DELETEALLITEMS = LVM_FIRST + 9 + LVM_GETCALLBACKMASK = LVM_FIRST + 10 + LVM_SETCALLBACKMASK = LVM_FIRST + 11 + LVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + LVM_GETNEXTITEM = LVM_FIRST + 12 + LVM_FINDITEM = LVM_FIRST + 83 + LVM_GETITEMRECT = LVM_FIRST + 14 + LVM_GETSTRINGWIDTH = LVM_FIRST + 87 + LVM_HITTEST = LVM_FIRST + 18 + LVM_ENSUREVISIBLE = LVM_FIRST + 19 + LVM_SCROLL = LVM_FIRST + 20 + LVM_REDRAWITEMS = LVM_FIRST + 21 + LVM_ARRANGE = LVM_FIRST + 22 + LVM_EDITLABEL = LVM_FIRST + 118 + LVM_GETEDITCONTROL = LVM_FIRST + 24 + LVM_GETCOLUMN = LVM_FIRST + 95 + LVM_SETCOLUMN = LVM_FIRST + 96 + LVM_INSERTCOLUMN = LVM_FIRST + 97 + LVM_DELETECOLUMN = LVM_FIRST + 28 + LVM_GETCOLUMNWIDTH = LVM_FIRST + 29 + LVM_SETCOLUMNWIDTH = LVM_FIRST + 30 + LVM_GETHEADER = LVM_FIRST + 31 + LVM_CREATEDRAGIMAGE = LVM_FIRST + 33 + LVM_GETVIEWRECT = LVM_FIRST + 34 + LVM_GETTEXTCOLOR = LVM_FIRST + 35 + LVM_SETTEXTCOLOR = LVM_FIRST + 36 + LVM_GETTEXTBKCOLOR = LVM_FIRST + 37 + LVM_SETTEXTBKCOLOR = LVM_FIRST + 38 + LVM_GETTOPINDEX = LVM_FIRST + 39 + LVM_GETCOUNTPERPAGE = LVM_FIRST + 40 + LVM_GETORIGIN = LVM_FIRST + 41 + LVM_UPDATE = LVM_FIRST + 42 + LVM_SETITEMSTATE = LVM_FIRST + 43 + LVM_GETITEMSTATE = LVM_FIRST + 44 + LVM_GETITEMTEXT = LVM_FIRST + 115 + LVM_SETITEMTEXT = LVM_FIRST + 116 + LVM_SETITEMCOUNT = LVM_FIRST + 47 + LVM_SORTITEMS = LVM_FIRST + 48 + LVM_SETITEMPOSITION32 = LVM_FIRST + 49 + LVM_GETSELECTEDCOUNT = LVM_FIRST + 50 + LVM_GETITEMSPACING = LVM_FIRST + 51 + LVM_GETISEARCHSTRING = LVM_FIRST + 117 + LVM_SETICONSPACING = LVM_FIRST + 53 + LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54 + LVM_GETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 55 + LVM_GETSUBITEMRECT = LVM_FIRST + 56 + LVM_SUBITEMHITTEST = LVM_FIRST + 57 + LVM_SETCOLUMNORDERARRAY = LVM_FIRST + 58 + LVM_GETCOLUMNORDERARRAY = LVM_FIRST + 59 + LVM_SETHOTITEM = LVM_FIRST + 60 + LVM_GETHOTITEM = LVM_FIRST + 61 + LVM_SETHOTCURSOR = LVM_FIRST + 62 + LVM_GETHOTCURSOR = LVM_FIRST + 63 + LVM_APPROXIMATEVIEWRECT = LVM_FIRST + 64 + LVM_SETWORKAREAS = LVM_FIRST + 65 + LVM_GETWORKAREAS = LVM_FIRST + 70 + LVM_GETNUMBEROFWORKAREAS = LVM_FIRST + 73 + LVM_GETSELECTIONMARK = LVM_FIRST + 66 + LVM_SETSELECTIONMARK = LVM_FIRST + 67 + LVM_SETHOVERTIME = LVM_FIRST + 71 + LVM_GETHOVERTIME = LVM_FIRST + 72 + LVM_SETTOOLTIPS = LVM_FIRST + 74 + LVM_GETTOOLTIPS = LVM_FIRST + 78 + LVM_SORTITEMSEX = LVM_FIRST + 81 + LVM_SETBKIMAGE = LVM_FIRST + 138 + LVM_GETBKIMAGE = LVM_FIRST + 139 + LVM_SETSELECTEDCOLUMN = LVM_FIRST + 140 + LVM_SETVIEW = LVM_FIRST + 142 + LVM_GETVIEW = LVM_FIRST + 143 + LVM_INSERTGROUP = LVM_FIRST + 145 + LVM_SETGROUPINFO = LVM_FIRST + 147 + LVM_GETGROUPINFO = LVM_FIRST + 149 + LVM_REMOVEGROUP = LVM_FIRST + 150 + LVM_MOVEGROUP = LVM_FIRST + 151 + LVM_GETGROUPCOUNT = LVM_FIRST + 152 + LVM_GETGROUPINFOBYINDEX = LVM_FIRST + 153 + LVM_MOVEITEMTOGROUP = LVM_FIRST + 154 + LVM_GETGROUPRECT = LVM_FIRST + 98 + LVM_SETGROUPMETRICS = LVM_FIRST + 155 + LVM_GETGROUPMETRICS = LVM_FIRST + 156 + LVM_ENABLEGROUPVIEW = LVM_FIRST + 157 + LVM_SORTGROUPS = LVM_FIRST + 158 + LVM_INSERTGROUPSORTED = LVM_FIRST + 159 + LVM_REMOVEALLGROUPS = LVM_FIRST + 160 + LVM_HASGROUP = LVM_FIRST + 161 + LVM_GETGROUPSTATE = LVM_FIRST + 92 + LVM_GETFOCUSEDGROUP = LVM_FIRST + 93 + LVM_SETTILEVIEWINFO = LVM_FIRST + 162 + LVM_GETTILEVIEWINFO = LVM_FIRST + 163 + LVM_SETTILEINFO = LVM_FIRST + 164 + LVM_GETTILEINFO = LVM_FIRST + 165 + LVM_SETINSERTMARK = LVM_FIRST + 166 + LVM_GETINSERTMARK = LVM_FIRST + 167 + LVM_INSERTMARKHITTEST = LVM_FIRST + 168 + LVM_GETINSERTMARKRECT = LVM_FIRST + 169 + LVM_SETINSERTMARKCOLOR = LVM_FIRST + 170 + LVM_GETINSERTMARKCOLOR = LVM_FIRST + 171 + LVM_SETINFOTIP = LVM_FIRST + 173 + LVM_GETSELECTEDCOLUMN = LVM_FIRST + 174 + LVM_ISGROUPVIEWENABLED = LVM_FIRST + 175 + LVM_GETOUTLINECOLOR = LVM_FIRST + 176 + LVM_SETOUTLINECOLOR = LVM_FIRST + 177 + LVM_CANCELEDITLABEL = LVM_FIRST + 179 + LVM_MAPINDEXTOID = LVM_FIRST + 180 + LVM_MAPIDTOINDEX = LVM_FIRST + 181 + LVM_ISITEMVISIBLE = LVM_FIRST + 182 + LVM_GETNEXTITEMINDEX = LVM_FIRST + 211 +) + +// ListView notifications +const ( + LVN_FIRST = -100 + + LVN_ITEMCHANGING = LVN_FIRST - 0 + LVN_ITEMCHANGED = LVN_FIRST - 1 + LVN_INSERTITEM = LVN_FIRST - 2 + LVN_DELETEITEM = LVN_FIRST - 3 + LVN_DELETEALLITEMS = LVN_FIRST - 4 + LVN_BEGINLABELEDITA = LVN_FIRST - 5 + LVN_BEGINLABELEDITW = LVN_FIRST - 75 + LVN_ENDLABELEDITA = LVN_FIRST - 6 + LVN_ENDLABELEDITW = LVN_FIRST - 76 + LVN_COLUMNCLICK = LVN_FIRST - 8 + LVN_BEGINDRAG = LVN_FIRST - 9 + LVN_BEGINRDRAG = LVN_FIRST - 11 + LVN_ODCACHEHINT = LVN_FIRST - 13 + LVN_ODFINDITEMA = LVN_FIRST - 52 + LVN_ODFINDITEMW = LVN_FIRST - 79 + LVN_ITEMACTIVATE = LVN_FIRST - 14 + LVN_ODSTATECHANGED = LVN_FIRST - 15 + LVN_HOTTRACK = LVN_FIRST - 21 + LVN_GETDISPINFO = LVN_FIRST - 77 + LVN_SETDISPINFO = LVN_FIRST - 78 + LVN_KEYDOWN = LVN_FIRST - 55 + LVN_MARQUEEBEGIN = LVN_FIRST - 56 + LVN_GETINFOTIP = LVN_FIRST - 58 + LVN_INCREMENTALSEARCH = LVN_FIRST - 63 + LVN_BEGINSCROLL = LVN_FIRST - 80 + LVN_ENDSCROLL = LVN_FIRST - 81 +) + +const ( + LVSCW_AUTOSIZE = ^uintptr(0) + LVSCW_AUTOSIZE_USEHEADER = ^uintptr(1) +) + +// ListView LVNI constants +const ( + LVNI_ALL = 0 + LVNI_FOCUSED = 1 + LVNI_SELECTED = 2 + LVNI_CUT = 4 + LVNI_DROPHILITED = 8 + LVNI_ABOVE = 256 + LVNI_BELOW = 512 + LVNI_TOLEFT = 1024 + LVNI_TORIGHT = 2048 +) + +// ListView styles +const ( + LVS_ICON = 0x0000 + LVS_REPORT = 0x0001 + LVS_SMALLICON = 0x0002 + LVS_LIST = 0x0003 + LVS_TYPEMASK = 0x0003 + LVS_SINGLESEL = 0x0004 + LVS_SHOWSELALWAYS = 0x0008 + LVS_SORTASCENDING = 0x0010 + LVS_SORTDESCENDING = 0x0020 + LVS_SHAREIMAGELISTS = 0x0040 + LVS_NOLABELWRAP = 0x0080 + LVS_AUTOARRANGE = 0x0100 + LVS_EDITLABELS = 0x0200 + LVS_OWNERDATA = 0x1000 + LVS_NOSCROLL = 0x2000 + LVS_TYPESTYLEMASK = 0xfc00 + LVS_ALIGNTOP = 0x0000 + LVS_ALIGNLEFT = 0x0800 + LVS_ALIGNMASK = 0x0c00 + LVS_OWNERDRAWFIXED = 0x0400 + LVS_NOCOLUMNHEADER = 0x4000 + LVS_NOSORTHEADER = 0x8000 +) + +// ListView extended styles +const ( + LVS_EX_GRIDLINES = 0x00000001 + LVS_EX_SUBITEMIMAGES = 0x00000002 + LVS_EX_CHECKBOXES = 0x00000004 + LVS_EX_TRACKSELECT = 0x00000008 + LVS_EX_HEADERDRAGDROP = 0x00000010 + LVS_EX_FULLROWSELECT = 0x00000020 + LVS_EX_ONECLICKACTIVATE = 0x00000040 + LVS_EX_TWOCLICKACTIVATE = 0x00000080 + LVS_EX_FLATSB = 0x00000100 + LVS_EX_REGIONAL = 0x00000200 + LVS_EX_INFOTIP = 0x00000400 + LVS_EX_UNDERLINEHOT = 0x00000800 + LVS_EX_UNDERLINECOLD = 0x00001000 + LVS_EX_MULTIWORKAREAS = 0x00002000 + LVS_EX_LABELTIP = 0x00004000 + LVS_EX_BORDERSELECT = 0x00008000 + LVS_EX_DOUBLEBUFFER = 0x00010000 + LVS_EX_HIDELABELS = 0x00020000 + LVS_EX_SINGLEROW = 0x00040000 + LVS_EX_SNAPTOGRID = 0x00080000 + LVS_EX_SIMPLESELECT = 0x00100000 +) + +// ListView column flags +const ( + LVCF_FMT = 0x0001 + LVCF_WIDTH = 0x0002 + LVCF_TEXT = 0x0004 + LVCF_SUBITEM = 0x0008 + LVCF_IMAGE = 0x0010 + LVCF_ORDER = 0x0020 +) + +// ListView column format constants +const ( + LVCFMT_LEFT = 0x0000 + LVCFMT_RIGHT = 0x0001 + LVCFMT_CENTER = 0x0002 + LVCFMT_JUSTIFYMASK = 0x0003 + LVCFMT_IMAGE = 0x0800 + LVCFMT_BITMAP_ON_RIGHT = 0x1000 + LVCFMT_COL_HAS_IMAGES = 0x8000 +) + +// ListView item flags +const ( + LVIF_TEXT = 0x00000001 + LVIF_IMAGE = 0x00000002 + LVIF_PARAM = 0x00000004 + LVIF_STATE = 0x00000008 + LVIF_INDENT = 0x00000010 + LVIF_NORECOMPUTE = 0x00000800 + LVIF_GROUPID = 0x00000100 + LVIF_COLUMNS = 0x00000200 +) + +const LVFI_PARAM = 0x0001 + +// ListView item states +const ( + LVIS_FOCUSED = 1 + LVIS_SELECTED = 2 + LVIS_CUT = 4 + LVIS_DROPHILITED = 8 + LVIS_OVERLAYMASK = 0xF00 + LVIS_STATEIMAGEMASK = 0xF000 +) + +// ListView hit test constants +const ( + LVHT_NOWHERE = 0x00000001 + LVHT_ONITEMICON = 0x00000002 + LVHT_ONITEMLABEL = 0x00000004 + LVHT_ONITEMSTATEICON = 0x00000008 + LVHT_ONITEM = LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON + + LVHT_ABOVE = 0x00000008 + LVHT_BELOW = 0x00000010 + LVHT_TORIGHT = 0x00000020 + LVHT_TOLEFT = 0x00000040 +) + +// ListView image list types +const ( + LVSIL_NORMAL = 0 + LVSIL_SMALL = 1 + LVSIL_STATE = 2 + LVSIL_GROUPHEADER = 3 +) + +// InitCommonControlsEx flags +const ( + ICC_LISTVIEW_CLASSES = 1 + ICC_TREEVIEW_CLASSES = 2 + ICC_BAR_CLASSES = 4 + ICC_TAB_CLASSES = 8 + ICC_UPDOWN_CLASS = 16 + ICC_PROGRESS_CLASS = 32 + ICC_HOTKEY_CLASS = 64 + ICC_ANIMATE_CLASS = 128 + ICC_WIN95_CLASSES = 255 + ICC_DATE_CLASSES = 256 + ICC_USEREX_CLASSES = 512 + ICC_COOL_CLASSES = 1024 + ICC_INTERNET_CLASSES = 2048 + ICC_PAGESCROLLER_CLASS = 4096 + ICC_NATIVEFNTCTL_CLASS = 8192 + INFOTIPSIZE = 1024 + ICC_STANDARD_CLASSES = 0x00004000 + ICC_LINK_CLASS = 0x00008000 +) + +// Dialog Codes +const ( + DLGC_WANTARROWS = 0x0001 + DLGC_WANTTAB = 0x0002 + DLGC_WANTALLKEYS = 0x0004 + DLGC_WANTMESSAGE = 0x0004 + DLGC_HASSETSEL = 0x0008 + DLGC_DEFPUSHBUTTON = 0x0010 + DLGC_UNDEFPUSHBUTTON = 0x0020 + DLGC_RADIOBUTTON = 0x0040 + DLGC_WANTCHARS = 0x0080 + DLGC_STATIC = 0x0100 + DLGC_BUTTON = 0x2000 +) + +// Get/SetWindowWord/Long offsets for use with WC_DIALOG windows +const ( + DWL_MSGRESULT = 0 + DWL_DLGPROC = 4 + DWL_USER = 8 +) + +// Registry predefined keys +const ( + HKEY_CLASSES_ROOT HKEY = 0x80000000 + HKEY_CURRENT_USER HKEY = 0x80000001 + HKEY_LOCAL_MACHINE HKEY = 0x80000002 + HKEY_USERS HKEY = 0x80000003 + HKEY_PERFORMANCE_DATA HKEY = 0x80000004 + HKEY_CURRENT_CONFIG HKEY = 0x80000005 + HKEY_DYN_DATA HKEY = 0x80000006 +) + +// Registry Key Security and Access Rights +const ( + KEY_ALL_ACCESS = 0xF003F + KEY_CREATE_SUB_KEY = 0x0004 + KEY_ENUMERATE_SUB_KEYS = 0x0008 + KEY_NOTIFY = 0x0010 + KEY_QUERY_VALUE = 0x0001 + KEY_SET_VALUE = 0x0002 + KEY_READ = 0x20019 + KEY_WRITE = 0x20006 +) + +const ( + NFR_ANSI = 1 + NFR_UNICODE = 2 + NF_QUERY = 3 + NF_REQUERY = 4 +) + +// Registry value types +const ( + RRF_RT_REG_NONE = 0x00000001 + RRF_RT_REG_SZ = 0x00000002 + RRF_RT_REG_EXPAND_SZ = 0x00000004 + RRF_RT_REG_BINARY = 0x00000008 + RRF_RT_REG_DWORD = 0x00000010 + RRF_RT_REG_MULTI_SZ = 0x00000020 + RRF_RT_REG_QWORD = 0x00000040 + RRF_RT_DWORD = RRF_RT_REG_BINARY | RRF_RT_REG_DWORD + RRF_RT_QWORD = RRF_RT_REG_BINARY | RRF_RT_REG_QWORD + RRF_RT_ANY = 0x0000ffff + RRF_NOEXPAND = 0x10000000 + RRF_ZEROONFAILURE = 0x20000000 + REG_PROCESS_APPKEY = 0x00000001 + REG_MUI_STRING_TRUNCATE = 0x00000001 +) + +// PeekMessage wRemoveMsg value +const ( + PM_NOREMOVE = 0x000 + PM_REMOVE = 0x001 + PM_NOYIELD = 0x002 +) + +// ImageList flags +const ( + ILC_MASK = 0x00000001 + ILC_COLOR = 0x00000000 + ILC_COLORDDB = 0x000000FE + ILC_COLOR4 = 0x00000004 + ILC_COLOR8 = 0x00000008 + ILC_COLOR16 = 0x00000010 + ILC_COLOR24 = 0x00000018 + ILC_COLOR32 = 0x00000020 + ILC_PALETTE = 0x00000800 + ILC_MIRROR = 0x00002000 + ILC_PERITEMMIRROR = 0x00008000 + ILC_ORIGINALSIZE = 0x00010000 + ILC_HIGHQUALITYSCALE = 0x00020000 +) + +// Keystroke Message Flags +const ( + KF_EXTENDED = 0x0100 + KF_DLGMODE = 0x0800 + KF_MENUMODE = 0x1000 + KF_ALTDOWN = 0x2000 + KF_REPEAT = 0x4000 + KF_UP = 0x8000 +) + +// Virtual-Key Codes +/* +const ( + VK_LBUTTON = 0x01 + VK_RBUTTON = 0x02 + VK_CANCEL = 0x03 + VK_MBUTTON = 0x04 + VK_XBUTTON1 = 0x05 + VK_XBUTTON2 = 0x06 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_CLEAR = 0x0C + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_PAUSE = 0x13 + VK_CAPITAL = 0x14 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 0x20 + VK_PRIOR = 0x21 + VK_NEXT = 0x22 + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_SELECT = 0x29 + VK_PRINT = 0x2A + VK_EXECUTE = 0x2B + VK_SNAPSHOT = 0x2C + VK_INSERT = 0x2D + VK_DELETE = 0x2E + VK_HELP = 0x2F + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_OEM_NEC_EQUAL = 0x92 + VK_OEM_FJ_JISHO = 0x92 + VK_OEM_FJ_MASSHOU = 0x93 + VK_OEM_FJ_TOUROKU = 0x94 + VK_OEM_FJ_LOYA = 0x95 + VK_OEM_FJ_ROYA = 0x96 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_AX = 0xE1 + VK_OEM_102 = 0xE2 + VK_ICO_HELP = 0xE3 + VK_ICO_00 = 0xE4 + VK_PROCESSKEY = 0xE5 + VK_ICO_CLEAR = 0xE6 + VK_OEM_RESET = 0xE9 + VK_OEM_JUMP = 0xEA + VK_OEM_PA1 = 0xEB + VK_OEM_PA2 = 0xEC + VK_OEM_PA3 = 0xED + VK_OEM_WSCTRL = 0xEE + VK_OEM_CUSEL = 0xEF + VK_OEM_ATTN = 0xF0 + VK_OEM_FINISH = 0xF1 + VK_OEM_COPY = 0xF2 + VK_OEM_AUTO = 0xF3 + VK_OEM_ENLW = 0xF4 + VK_OEM_BACKTAB = 0xF5 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +)*/ + +// Registry Value Types +const ( + REG_NONE = 0 + REG_SZ = 1 + REG_EXPAND_SZ = 2 + REG_BINARY = 3 + REG_DWORD = 4 + REG_DWORD_LITTLE_ENDIAN = 4 + REG_DWORD_BIG_ENDIAN = 5 + REG_LINK = 6 + REG_MULTI_SZ = 7 + REG_RESOURCE_LIST = 8 + REG_FULL_RESOURCE_DESCRIPTOR = 9 + REG_RESOURCE_REQUIREMENTS_LIST = 10 + REG_QWORD = 11 + REG_QWORD_LITTLE_ENDIAN = 11 +) + +// Tooltip styles +const ( + TTS_ALWAYSTIP = 0x01 + TTS_NOPREFIX = 0x02 + TTS_NOANIMATE = 0x10 + TTS_NOFADE = 0x20 + TTS_BALLOON = 0x40 + TTS_CLOSE = 0x80 + TTS_USEVISUALSTYLE = 0x100 +) + +// Tooltip messages +const ( + TTM_ACTIVATE = WM_USER + 1 + TTM_SETDELAYTIME = WM_USER + 3 + TTM_ADDTOOL = WM_USER + 50 + TTM_DELTOOL = WM_USER + 51 + TTM_NEWTOOLRECT = WM_USER + 52 + TTM_RELAYEVENT = WM_USER + 7 + TTM_GETTOOLINFO = WM_USER + 53 + TTM_SETTOOLINFO = WM_USER + 54 + TTM_HITTEST = WM_USER + 55 + TTM_GETTEXT = WM_USER + 56 + TTM_UPDATETIPTEXT = WM_USER + 57 + TTM_GETTOOLCOUNT = WM_USER + 13 + TTM_ENUMTOOLS = WM_USER + 58 + TTM_GETCURRENTTOOL = WM_USER + 59 + TTM_WINDOWFROMPOINT = WM_USER + 16 + TTM_TRACKACTIVATE = WM_USER + 17 + TTM_TRACKPOSITION = WM_USER + 18 + TTM_SETTIPBKCOLOR = WM_USER + 19 + TTM_SETTIPTEXTCOLOR = WM_USER + 20 + TTM_GETDELAYTIME = WM_USER + 21 + TTM_GETTIPBKCOLOR = WM_USER + 22 + TTM_GETTIPTEXTCOLOR = WM_USER + 23 + TTM_SETMAXTIPWIDTH = WM_USER + 24 + TTM_GETMAXTIPWIDTH = WM_USER + 25 + TTM_SETMARGIN = WM_USER + 26 + TTM_GETMARGIN = WM_USER + 27 + TTM_POP = WM_USER + 28 + TTM_UPDATE = WM_USER + 29 + TTM_GETBUBBLESIZE = WM_USER + 30 + TTM_ADJUSTRECT = WM_USER + 31 + TTM_SETTITLE = WM_USER + 33 + TTM_POPUP = WM_USER + 34 + TTM_GETTITLE = WM_USER + 35 +) + +// Tooltip icons +const ( + TTI_NONE = 0 + TTI_INFO = 1 + TTI_WARNING = 2 + TTI_ERROR = 3 + TTI_INFO_LARGE = 4 + TTI_WARNING_LARGE = 5 + TTI_ERROR_LARGE = 6 +) + +// Tooltip notifications +const ( + TTN_FIRST = -520 + TTN_LAST = -549 + TTN_GETDISPINFO = TTN_FIRST - 10 + TTN_SHOW = TTN_FIRST - 1 + TTN_POP = TTN_FIRST - 2 + TTN_LINKCLICK = TTN_FIRST - 3 + TTN_NEEDTEXT = TTN_GETDISPINFO +) + +const ( + TTF_IDISHWND = 0x0001 + TTF_CENTERTIP = 0x0002 + TTF_RTLREADING = 0x0004 + TTF_SUBCLASS = 0x0010 + TTF_TRACK = 0x0020 + TTF_ABSOLUTE = 0x0080 + TTF_TRANSPARENT = 0x0100 + TTF_PARSELINKS = 0x1000 + TTF_DI_SETITEM = 0x8000 +) + +const ( + SWP_NOSIZE = 0x0001 + SWP_NOMOVE = 0x0002 + SWP_NOZORDER = 0x0004 + SWP_NOREDRAW = 0x0008 + SWP_NOACTIVATE = 0x0010 + SWP_FRAMECHANGED = 0x0020 + SWP_SHOWWINDOW = 0x0040 + SWP_HIDEWINDOW = 0x0080 + SWP_NOCOPYBITS = 0x0100 + SWP_NOOWNERZORDER = 0x0200 + SWP_NOSENDCHANGING = 0x0400 + SWP_DRAWFRAME = SWP_FRAMECHANGED + SWP_NOREPOSITION = SWP_NOOWNERZORDER + SWP_DEFERERASE = 0x2000 + SWP_ASYNCWINDOWPOS = 0x4000 +) + +// Predefined window handles +const ( + HWND_BROADCAST = HWND(0xFFFF) + HWND_BOTTOM = HWND(1) + HWND_NOTOPMOST = ^HWND(1) // -2 + HWND_TOP = HWND(0) + HWND_TOPMOST = ^HWND(0) // -1 + HWND_DESKTOP = HWND(0) + HWND_MESSAGE = ^HWND(2) // -3 +) + +// Pen types +const ( + PS_COSMETIC = 0x00000000 + PS_GEOMETRIC = 0x00010000 + PS_TYPE_MASK = 0x000F0000 +) + +// Pen styles +const ( + PS_SOLID = 0 + PS_DASH = 1 + PS_DOT = 2 + PS_DASHDOT = 3 + PS_DASHDOTDOT = 4 + PS_NULL = 5 + PS_INSIDEFRAME = 6 + PS_USERSTYLE = 7 + PS_ALTERNATE = 8 + PS_STYLE_MASK = 0x0000000F +) + +// Pen cap types +const ( + PS_ENDCAP_ROUND = 0x00000000 + PS_ENDCAP_SQUARE = 0x00000100 + PS_ENDCAP_FLAT = 0x00000200 + PS_ENDCAP_MASK = 0x00000F00 +) + +// Pen join types +const ( + PS_JOIN_ROUND = 0x00000000 + PS_JOIN_BEVEL = 0x00001000 + PS_JOIN_MITER = 0x00002000 + PS_JOIN_MASK = 0x0000F000 +) + +// Hatch styles +const ( + HS_HORIZONTAL = 0 + HS_VERTICAL = 1 + HS_FDIAGONAL = 2 + HS_BDIAGONAL = 3 + HS_CROSS = 4 + HS_DIAGCROSS = 5 +) + +// Stock Logical Objects +const ( + WHITE_BRUSH = 0 + LTGRAY_BRUSH = 1 + GRAY_BRUSH = 2 + DKGRAY_BRUSH = 3 + BLACK_BRUSH = 4 + NULL_BRUSH = 5 + HOLLOW_BRUSH = NULL_BRUSH + WHITE_PEN = 6 + BLACK_PEN = 7 + NULL_PEN = 8 + OEM_FIXED_FONT = 10 + ANSI_FIXED_FONT = 11 + ANSI_VAR_FONT = 12 + SYSTEM_FONT = 13 + DEVICE_DEFAULT_FONT = 14 + DEFAULT_PALETTE = 15 + SYSTEM_FIXED_FONT = 16 + DEFAULT_GUI_FONT = 17 + DC_BRUSH = 18 + DC_PEN = 19 +) + +// Brush styles +const ( + BS_SOLID = 0 + BS_NULL = 1 + BS_HOLLOW = BS_NULL + BS_HATCHED = 2 + BS_PATTERN = 3 + BS_INDEXED = 4 + BS_DIBPATTERN = 5 + BS_DIBPATTERNPT = 6 + BS_PATTERN8X8 = 7 + BS_DIBPATTERN8X8 = 8 + BS_MONOPATTERN = 9 +) + +// TRACKMOUSEEVENT flags +const ( + TME_HOVER = 0x00000001 + TME_LEAVE = 0x00000002 + TME_NONCLIENT = 0x00000010 + TME_QUERY = 0x40000000 + TME_CANCEL = 0x80000000 + + HOVER_DEFAULT = 0xFFFFFFFF +) + +// WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes +const ( + HTERROR = -2 + HTTRANSPARENT = -1 + HTNOWHERE = 0 + HTCLIENT = 1 + HTCAPTION = 2 + HTSYSMENU = 3 + HTGROWBOX = 4 + HTSIZE = HTGROWBOX + HTMENU = 5 + HTHSCROLL = 6 + HTVSCROLL = 7 + HTMINBUTTON = 8 + HTMAXBUTTON = 9 + HTLEFT = 10 + HTRIGHT = 11 + HTTOP = 12 + HTTOPLEFT = 13 + HTTOPRIGHT = 14 + HTBOTTOM = 15 + HTBOTTOMLEFT = 16 + HTBOTTOMRIGHT = 17 + HTBORDER = 18 + HTREDUCE = HTMINBUTTON + HTZOOM = HTMAXBUTTON + HTSIZEFIRST = HTLEFT + HTSIZELAST = HTBOTTOMRIGHT + HTOBJECT = 19 + HTCLOSE = 20 + HTHELP = 21 +) + +// DrawText[Ex] format flags +const ( + DT_TOP = 0x00000000 + DT_LEFT = 0x00000000 + DT_CENTER = 0x00000001 + DT_RIGHT = 0x00000002 + DT_VCENTER = 0x00000004 + DT_BOTTOM = 0x00000008 + DT_WORDBREAK = 0x00000010 + DT_SINGLELINE = 0x00000020 + DT_EXPANDTABS = 0x00000040 + DT_TABSTOP = 0x00000080 + DT_NOCLIP = 0x00000100 + DT_EXTERNALLEADING = 0x00000200 + DT_CALCRECT = 0x00000400 + DT_NOPREFIX = 0x00000800 + DT_INTERNAL = 0x00001000 + DT_EDITCONTROL = 0x00002000 + DT_PATH_ELLIPSIS = 0x00004000 + DT_END_ELLIPSIS = 0x00008000 + DT_MODIFYSTRING = 0x00010000 + DT_RTLREADING = 0x00020000 + DT_WORD_ELLIPSIS = 0x00040000 + DT_NOFULLWIDTHCHARBREAK = 0x00080000 + DT_HIDEPREFIX = 0x00100000 + DT_PREFIXONLY = 0x00200000 +) + +const CLR_INVALID = 0xFFFFFFFF + +// Background Modes +const ( + TRANSPARENT = 1 + OPAQUE = 2 + BKMODE_LAST = 2 +) + +// Global Memory Flags +const ( + GMEM_FIXED = 0x0000 + GMEM_MOVEABLE = 0x0002 + GMEM_NOCOMPACT = 0x0010 + GMEM_NODISCARD = 0x0020 + GMEM_ZEROINIT = 0x0040 + GMEM_MODIFY = 0x0080 + GMEM_DISCARDABLE = 0x0100 + GMEM_NOT_BANKED = 0x1000 + GMEM_SHARE = 0x2000 + GMEM_DDESHARE = 0x2000 + GMEM_NOTIFY = 0x4000 + GMEM_LOWER = GMEM_NOT_BANKED + GMEM_VALID_FLAGS = 0x7F72 + GMEM_INVALID_HANDLE = 0x8000 + GHND = GMEM_MOVEABLE | GMEM_ZEROINIT + GPTR = GMEM_FIXED | GMEM_ZEROINIT +) + +// Ternary raster operations +const ( + SRCCOPY = 0x00CC0020 + SRCPAINT = 0x00EE0086 + SRCAND = 0x008800C6 + SRCINVERT = 0x00660046 + SRCERASE = 0x00440328 + NOTSRCCOPY = 0x00330008 + NOTSRCERASE = 0x001100A6 + MERGECOPY = 0x00C000CA + MERGEPAINT = 0x00BB0226 + PATCOPY = 0x00F00021 + PATPAINT = 0x00FB0A09 + PATINVERT = 0x005A0049 + DSTINVERT = 0x00550009 + BLACKNESS = 0x00000042 + WHITENESS = 0x00FF0062 + NOMIRRORBITMAP = 0x80000000 + CAPTUREBLT = 0x40000000 +) + +// Clipboard formats +const ( + CF_TEXT = 1 + CF_BITMAP = 2 + CF_METAFILEPICT = 3 + CF_SYLK = 4 + CF_DIF = 5 + CF_TIFF = 6 + CF_OEMTEXT = 7 + CF_DIB = 8 + CF_PALETTE = 9 + CF_PENDATA = 10 + CF_RIFF = 11 + CF_WAVE = 12 + CF_UNICODETEXT = 13 + CF_ENHMETAFILE = 14 + CF_HDROP = 15 + CF_LOCALE = 16 + CF_DIBV5 = 17 + CF_MAX = 18 + CF_OWNERDISPLAY = 0x0080 + CF_DSPTEXT = 0x0081 + CF_DSPBITMAP = 0x0082 + CF_DSPMETAFILEPICT = 0x0083 + CF_DSPENHMETAFILE = 0x008E + CF_PRIVATEFIRST = 0x0200 + CF_PRIVATELAST = 0x02FF + CF_GDIOBJFIRST = 0x0300 + CF_GDIOBJLAST = 0x03FF +) + +// Bitmap compression formats +const ( + BI_RGB = 0 + BI_RLE8 = 1 + BI_RLE4 = 2 + BI_BITFIELDS = 3 + BI_JPEG = 4 + BI_PNG = 5 +) + +// SetDIBitsToDevice fuColorUse +const ( + DIB_PAL_COLORS = 1 + DIB_RGB_COLORS = 0 +) + +const ( + STANDARD_RIGHTS_REQUIRED = 0x000F +) + +// Service Control Manager object specific access types +const ( + SC_MANAGER_CONNECT = 0x0001 + SC_MANAGER_CREATE_SERVICE = 0x0002 + SC_MANAGER_ENUMERATE_SERVICE = 0x0004 + SC_MANAGER_LOCK = 0x0008 + SC_MANAGER_QUERY_LOCK_STATUS = 0x0010 + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020 + SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_LOCK | SC_MANAGER_QUERY_LOCK_STATUS | SC_MANAGER_MODIFY_BOOT_CONFIG +) + +// Service Types (Bit Mask) +const ( + SERVICE_KERNEL_DRIVER = 0x00000001 + SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 + SERVICE_ADAPTER = 0x00000004 + SERVICE_RECOGNIZER_DRIVER = 0x00000008 + SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER + SERVICE_WIN32_OWN_PROCESS = 0x00000010 + SERVICE_WIN32_SHARE_PROCESS = 0x00000020 + SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS + SERVICE_INTERACTIVE_PROCESS = 0x00000100 + SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS +) + +// Service State -- for CurrentState +const ( + SERVICE_STOPPED = 0x00000001 + SERVICE_START_PENDING = 0x00000002 + SERVICE_STOP_PENDING = 0x00000003 + SERVICE_RUNNING = 0x00000004 + SERVICE_CONTINUE_PENDING = 0x00000005 + SERVICE_PAUSE_PENDING = 0x00000006 + SERVICE_PAUSED = 0x00000007 +) + +// Controls Accepted (Bit Mask) +const ( + SERVICE_ACCEPT_STOP = 0x00000001 + SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002 + SERVICE_ACCEPT_SHUTDOWN = 0x00000004 + SERVICE_ACCEPT_PARAMCHANGE = 0x00000008 + SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010 + SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020 + SERVICE_ACCEPT_POWEREVENT = 0x00000040 + SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080 + SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100 + SERVICE_ACCEPT_TIMECHANGE = 0x00000200 + SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400 +) + +// Service object specific access type +const ( + SERVICE_QUERY_CONFIG = 0x0001 + SERVICE_CHANGE_CONFIG = 0x0002 + SERVICE_QUERY_STATUS = 0x0004 + SERVICE_ENUMERATE_DEPENDENTS = 0x0008 + SERVICE_START = 0x0010 + SERVICE_STOP = 0x0020 + SERVICE_PAUSE_CONTINUE = 0x0040 + SERVICE_INTERROGATE = 0x0080 + SERVICE_USER_DEFINED_CONTROL = 0x0100 + + SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | + SERVICE_QUERY_CONFIG | + SERVICE_CHANGE_CONFIG | + SERVICE_QUERY_STATUS | + SERVICE_ENUMERATE_DEPENDENTS | + SERVICE_START | + SERVICE_STOP | + SERVICE_PAUSE_CONTINUE | + SERVICE_INTERROGATE | + SERVICE_USER_DEFINED_CONTROL +) + +// MapVirtualKey maptypes +const ( + MAPVK_VK_TO_CHAR = 2 + MAPVK_VK_TO_VSC = 0 + MAPVK_VSC_TO_VK = 1 + MAPVK_VSC_TO_VK_EX = 3 +) + +// ReadEventLog Flags +const ( + EVENTLOG_SEEK_READ = 0x0002 + EVENTLOG_SEQUENTIAL_READ = 0x0001 + EVENTLOG_FORWARDS_READ = 0x0004 + EVENTLOG_BACKWARDS_READ = 0x0008 +) + +// CreateToolhelp32Snapshot flags +const ( + TH32CS_SNAPHEAPLIST = 0x00000001 + TH32CS_SNAPPROCESS = 0x00000002 + TH32CS_SNAPTHREAD = 0x00000004 + TH32CS_SNAPMODULE = 0x00000008 + TH32CS_SNAPMODULE32 = 0x00000010 + TH32CS_INHERIT = 0x80000000 + TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD +) + +const ( + MAX_MODULE_NAME32 = 255 + MAX_PATH = 260 +) + +const ( + NIM_ADD = 0x00000000 + NIM_MODIFY = 0x00000001 + NIM_DELETE = 0x00000002 + NIM_SETVERSION = 0x00000004 + + NIF_MESSAGE = 0x00000001 + NIF_ICON = 0x00000002 + NIF_TIP = 0x00000004 + NIF_STATE = 0x00000008 + NIF_INFO = 0x00000010 + + NIS_HIDDEN = 0x00000001 + + NIIF_NONE = 0x00000000 + NIIF_INFO = 0x00000001 + NIIF_WARNING = 0x00000002 + NIIF_ERROR = 0x00000003 + NIIF_USER = 0x00000004 + NIIF_NOSOUND = 0x00000010 + NIIF_LARGE_ICON = 0x00000020 + NIIF_RESPECT_QUIET_TIME = 0x00000080 + NIIF_ICON_MASK = 0x0000000F +) + +const ( + FOREGROUND_BLUE = 0x0001 + FOREGROUND_GREEN = 0x0002 + FOREGROUND_RED = 0x0004 + FOREGROUND_INTENSITY = 0x0008 + BACKGROUND_BLUE = 0x0010 + BACKGROUND_GREEN = 0x0020 + BACKGROUND_RED = 0x0040 + BACKGROUND_INTENSITY = 0x0080 + COMMON_LVB_LEADING_BYTE = 0x0100 + COMMON_LVB_TRAILING_BYTE = 0x0200 + COMMON_LVB_GRID_HORIZONTAL = 0x0400 + COMMON_LVB_GRID_LVERTICAL = 0x0800 + COMMON_LVB_GRID_RVERTICAL = 0x1000 + COMMON_LVB_REVERSE_VIDEO = 0x4000 + COMMON_LVB_UNDERSCORE = 0x8000 +) + +// Flags used by the DWM_BLURBEHIND structure to indicate +// which of its members contain valid information. +const ( + DWM_BB_ENABLE = 0x00000001 // A value for the fEnable member has been specified. + DWM_BB_BLURREGION = 0x00000002 // A value for the hRgnBlur member has been specified. + DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004 // A value for the fTransitionOnMaximized member has been specified. +) + +// Flags used by the DwmEnableComposition function +// to change the state of Desktop Window Manager (DWM) composition. +const ( + DWM_EC_DISABLECOMPOSITION = 0 // Disable composition + DWM_EC_ENABLECOMPOSITION = 1 // Enable composition +) + +// enum-lite implementation for the following constant structure +type DWM_SHOWCONTACT int32 + +const ( + DWMSC_DOWN = 0x00000001 + DWMSC_UP = 0x00000002 + DWMSC_DRAG = 0x00000004 + DWMSC_HOLD = 0x00000008 + DWMSC_PENBARREL = 0x00000010 + DWMSC_NONE = 0x00000000 + DWMSC_ALL = 0xFFFFFFFF +) + +// enum-lite implementation for the following constant structure +type DWM_SOURCE_FRAME_SAMPLING int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetPresentParameters function +// to specify the frame sampling type +const ( + DWM_SOURCE_FRAME_SAMPLING_POINT = iota + 1 + DWM_SOURCE_FRAME_SAMPLING_COVERAGE + DWM_SOURCE_FRAME_SAMPLING_LAST +) + +// Flags used by the DWM_THUMBNAIL_PROPERTIES structure to +// indicate which of its members contain valid information. +const ( + DWM_TNP_RECTDESTINATION = 0x00000001 // A value for the rcDestination member has been specified + DWM_TNP_RECTSOURCE = 0x00000002 // A value for the rcSource member has been specified + DWM_TNP_OPACITY = 0x00000004 // A value for the opacity member has been specified + DWM_TNP_VISIBLE = 0x00000008 // A value for the fVisible member has been specified + DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010 // A value for the fSourceClientAreaOnly member has been specified +) + +// enum-lite implementation for the following constant structure +type DWMFLIP3DWINDOWPOLICY int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetWindowAttribute function +// to specify the Flip3D window policy +const ( + DWMFLIP3D_DEFAULT = iota + 1 + DWMFLIP3D_EXCLUDEBELOW + DWMFLIP3D_EXCLUDEABOVE + DWMFLIP3D_LAST +) + +// enum-lite implementation for the following constant structure +type DWMNCRENDERINGPOLICY int32 + +// TODO: need to verify this construction +// Flags used by the DwmSetWindowAttribute function +// to specify the non-client area rendering policy +const ( + DWMNCRP_USEWINDOWSTYLE = iota + 1 + DWMNCRP_DISABLED + DWMNCRP_ENABLED + DWMNCRP_LAST +) + +// enum-lite implementation for the following constant structure +type DWMTRANSITION_OWNEDWINDOW_TARGET int32 + +const ( + DWMTRANSITION_OWNEDWINDOW_NULL = -1 + DWMTRANSITION_OWNEDWINDOW_REPOSITION = 0 +) + +// TODO: need to verify this construction +// Flags used by the DwmGetWindowAttribute and DwmSetWindowAttribute functions +// to specify window attributes for non-client rendering +const ( + DWMWA_NCRENDERING_ENABLED = iota + 1 + DWMWA_NCRENDERING_POLICY + DWMWA_TRANSITIONS_FORCEDISABLED + DWMWA_ALLOW_NCPAINT + DWMWA_CAPTION_BUTTON_BOUNDS + DWMWA_NONCLIENT_RTL_LAYOUT + DWMWA_FORCE_ICONIC_REPRESENTATION + DWMWA_FLIP3D_POLICY + DWMWA_EXTENDED_FRAME_BOUNDS + DWMWA_HAS_ICONIC_BITMAP + DWMWA_DISALLOW_PEEK + DWMWA_EXCLUDED_FROM_PEEK + DWMWA_CLOAK + DWMWA_CLOAKED + DWMWA_FREEZE_REPRESENTATION + DWMWA_LAST +) + +// enum-lite implementation for the following constant structure +type GESTURE_TYPE int32 + +// TODO: use iota? +// Identifies the gesture type +const ( + GT_PEN_TAP = 0 + GT_PEN_DOUBLETAP = 1 + GT_PEN_RIGHTTAP = 2 + GT_PEN_PRESSANDHOLD = 3 + GT_PEN_PRESSANDHOLDABORT = 4 + GT_TOUCH_TAP = 5 + GT_TOUCH_DOUBLETAP = 6 + GT_TOUCH_RIGHTTAP = 7 + GT_TOUCH_PRESSANDHOLD = 8 + GT_TOUCH_PRESSANDHOLDABORT = 9 + GT_TOUCH_PRESSANDTAP = 10 +) + +// Icons +const ( + ICON_SMALL = 0 + ICON_BIG = 1 + ICON_SMALL2 = 2 +) + +const ( + SIZE_RESTORED = 0 + SIZE_MINIMIZED = 1 + SIZE_MAXIMIZED = 2 + SIZE_MAXSHOW = 3 + SIZE_MAXHIDE = 4 +) + +// XButton values +const ( + XBUTTON1 = 1 + XBUTTON2 = 2 +) + +const ( + LR_LOADFROMFILE = 0x00000010 + LR_DEFAULTSIZE = 0x00000040 +) + +// Devmode +const ( + DM_SPECVERSION = 0x0401 + + DM_ORIENTATION = 0x00000001 + DM_PAPERSIZE = 0x00000002 + DM_PAPERLENGTH = 0x00000004 + DM_PAPERWIDTH = 0x00000008 + DM_SCALE = 0x00000010 + DM_POSITION = 0x00000020 + DM_NUP = 0x00000040 + DM_DISPLAYORIENTATION = 0x00000080 + DM_COPIES = 0x00000100 + DM_DEFAULTSOURCE = 0x00000200 + DM_PRINTQUALITY = 0x00000400 + DM_COLOR = 0x00000800 + DM_DUPLEX = 0x00001000 + DM_YRESOLUTION = 0x00002000 + DM_TTOPTION = 0x00004000 + DM_COLLATE = 0x00008000 + DM_FORMNAME = 0x00010000 + DM_LOGPIXELS = 0x00020000 + DM_BITSPERPEL = 0x00040000 + DM_PELSWIDTH = 0x00080000 + DM_PELSHEIGHT = 0x00100000 + DM_DISPLAYFLAGS = 0x00200000 + DM_DISPLAYFREQUENCY = 0x00400000 + DM_ICMMETHOD = 0x00800000 + DM_ICMINTENT = 0x01000000 + DM_MEDIATYPE = 0x02000000 + DM_DITHERTYPE = 0x04000000 + DM_PANNINGWIDTH = 0x08000000 + DM_PANNINGHEIGHT = 0x10000000 + DM_DISPLAYFIXEDOUTPUT = 0x20000000 +) + +// ChangeDisplaySettings +const ( + CDS_UPDATEREGISTRY = 0x00000001 + CDS_TEST = 0x00000002 + CDS_FULLSCREEN = 0x00000004 + CDS_GLOBAL = 0x00000008 + CDS_SET_PRIMARY = 0x00000010 + CDS_VIDEOPARAMETERS = 0x00000020 + CDS_RESET = 0x40000000 + CDS_NORESET = 0x10000000 + + DISP_CHANGE_SUCCESSFUL = 0 + DISP_CHANGE_RESTART = 1 + DISP_CHANGE_FAILED = -1 + DISP_CHANGE_BADMODE = -2 + DISP_CHANGE_NOTUPDATED = -3 + DISP_CHANGE_BADFLAGS = -4 + DISP_CHANGE_BADPARAM = -5 + DISP_CHANGE_BADDUALVIEW = -6 +) + +const ( + ENUM_CURRENT_SETTINGS = 0xFFFFFFFF + ENUM_REGISTRY_SETTINGS = 0xFFFFFFFE +) + +// PIXELFORMATDESCRIPTOR +const ( + PFD_TYPE_RGBA = 0 + PFD_TYPE_COLORINDEX = 1 + + PFD_MAIN_PLANE = 0 + PFD_OVERLAY_PLANE = 1 + PFD_UNDERLAY_PLANE = -1 + + PFD_DOUBLEBUFFER = 0x00000001 + PFD_STEREO = 0x00000002 + PFD_DRAW_TO_WINDOW = 0x00000004 + PFD_DRAW_TO_BITMAP = 0x00000008 + PFD_SUPPORT_GDI = 0x00000010 + PFD_SUPPORT_OPENGL = 0x00000020 + PFD_GENERIC_FORMAT = 0x00000040 + PFD_NEED_PALETTE = 0x00000080 + PFD_NEED_SYSTEM_PALETTE = 0x00000100 + PFD_SWAP_EXCHANGE = 0x00000200 + PFD_SWAP_COPY = 0x00000400 + PFD_SWAP_LAYER_BUFFERS = 0x00000800 + PFD_GENERIC_ACCELERATED = 0x00001000 + PFD_SUPPORT_DIRECTDRAW = 0x00002000 + PFD_DIRECT3D_ACCELERATED = 0x00004000 + PFD_SUPPORT_COMPOSITION = 0x00008000 + + PFD_DEPTH_DONTCARE = 0x20000000 + PFD_DOUBLEBUFFER_DONTCARE = 0x40000000 + PFD_STEREO_DONTCARE = 0x80000000 +) + +const ( + INPUT_MOUSE = 0 + INPUT_KEYBOARD = 1 + INPUT_HARDWARE = 2 +) + +const ( + MOUSEEVENTF_ABSOLUTE = 0x8000 + MOUSEEVENTF_HWHEEL = 0x01000 + MOUSEEVENTF_MOVE = 0x0001 + MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000 + MOUSEEVENTF_LEFTDOWN = 0x0002 + MOUSEEVENTF_LEFTUP = 0x0004 + MOUSEEVENTF_RIGHTDOWN = 0x0008 + MOUSEEVENTF_RIGHTUP = 0x0010 + MOUSEEVENTF_MIDDLEDOWN = 0x0020 + MOUSEEVENTF_MIDDLEUP = 0x0040 + MOUSEEVENTF_VIRTUALDESK = 0x4000 + MOUSEEVENTF_WHEEL = 0x0800 + MOUSEEVENTF_XDOWN = 0x0080 + MOUSEEVENTF_XUP = 0x0100 +) + +// Windows Hooks (WH_*) +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx +const ( + WH_CALLWNDPROC = 4 + WH_CALLWNDPROCRET = 12 + WH_CBT = 5 + WH_DEBUG = 9 + WH_FOREGROUNDIDLE = 11 + WH_GETMESSAGE = 3 + WH_JOURNALPLAYBACK = 1 + WH_JOURNALRECORD = 0 + WH_KEYBOARD = 2 + WH_KEYBOARD_LL = 13 + WH_MOUSE = 7 + WH_MOUSE_LL = 14 + WH_MSGFILTER = -1 + WH_SHELL = 10 + WH_SYSMSGFILTER = 6 +) + +// ComboBox return values +const ( + CB_OKAY = 0 + CB_ERR = ^uintptr(0) // -1 + CB_ERRSPACE = ^uintptr(1) // -2 +) + +// ComboBox notifications +const ( + CBN_ERRSPACE = -1 + CBN_SELCHANGE = 1 + CBN_DBLCLK = 2 + CBN_SETFOCUS = 3 + CBN_KILLFOCUS = 4 + CBN_EDITCHANGE = 5 + CBN_EDITUPDATE = 6 + CBN_DROPDOWN = 7 + CBN_CLOSEUP = 8 + CBN_SELENDOK = 9 + CBN_SELENDCANCEL = 10 +) + +// ComboBox styles +const ( + CBS_SIMPLE = 0x0001 + CBS_DROPDOWN = 0x0002 + CBS_DROPDOWNLIST = 0x0003 + CBS_OWNERDRAWFIXED = 0x0010 + CBS_OWNERDRAWVARIABLE = 0x0020 + CBS_AUTOHSCROLL = 0x0040 + CBS_OEMCONVERT = 0x0080 + CBS_SORT = 0x0100 + CBS_HASSTRINGS = 0x0200 + CBS_NOINTEGRALHEIGHT = 0x0400 + CBS_DISABLENOSCROLL = 0x0800 + CBS_UPPERCASE = 0x2000 + CBS_LOWERCASE = 0x4000 +) + +// ComboBox messages +const ( + CB_GETEDITSEL = 0x0140 + CB_LIMITTEXT = 0x0141 + CB_SETEDITSEL = 0x0142 + CB_ADDSTRING = 0x0143 + CB_DELETESTRING = 0x0144 + CB_DIR = 0x0145 + CB_GETCOUNT = 0x0146 + CB_GETCURSEL = 0x0147 + CB_GETLBTEXT = 0x0148 + CB_GETLBTEXTLEN = 0x0149 + CB_INSERTSTRING = 0x014A + CB_RESETCONTENT = 0x014B + CB_FINDSTRING = 0x014C + CB_SELECTSTRING = 0x014D + CB_SETCURSEL = 0x014E + CB_SHOWDROPDOWN = 0x014F + CB_GETITEMDATA = 0x0150 + CB_SETITEMDATA = 0x0151 + CB_GETDROPPEDCONTROLRECT = 0x0152 + CB_SETITEMHEIGHT = 0x0153 + CB_GETITEMHEIGHT = 0x0154 + CB_SETEXTENDEDUI = 0x0155 + CB_GETEXTENDEDUI = 0x0156 + CB_GETDROPPEDSTATE = 0x0157 + CB_FINDSTRINGEXACT = 0x0158 + CB_SETLOCALE = 0x0159 + CB_GETLOCALE = 0x015A + CB_GETTOPINDEX = 0x015b + CB_SETTOPINDEX = 0x015c + CB_GETHORIZONTALEXTENT = 0x015d + CB_SETHORIZONTALEXTENT = 0x015e + CB_GETDROPPEDWIDTH = 0x015f + CB_SETDROPPEDWIDTH = 0x0160 + CB_INITSTORAGE = 0x0161 + CB_MULTIPLEADDSTRING = 0x0163 + CB_GETCOMBOBOXINFO = 0x0164 +) + +// TreeView styles +const ( + TVS_HASBUTTONS = 0x0001 + TVS_HASLINES = 0x0002 + TVS_LINESATROOT = 0x0004 + TVS_EDITLABELS = 0x0008 + TVS_DISABLEDRAGDROP = 0x0010 + TVS_SHOWSELALWAYS = 0x0020 + TVS_RTLREADING = 0x0040 + TVS_NOTOOLTIPS = 0x0080 + TVS_CHECKBOXES = 0x0100 + TVS_TRACKSELECT = 0x0200 + TVS_SINGLEEXPAND = 0x0400 + TVS_INFOTIP = 0x0800 + TVS_FULLROWSELECT = 0x1000 + TVS_NOSCROLL = 0x2000 + TVS_NONEVENHEIGHT = 0x4000 + TVS_NOHSCROLL = 0x8000 +) + +const ( + TVS_EX_NOSINGLECOLLAPSE = 0x0001 + TVS_EX_MULTISELECT = 0x0002 + TVS_EX_DOUBLEBUFFER = 0x0004 + TVS_EX_NOINDENTSTATE = 0x0008 + TVS_EX_RICHTOOLTIP = 0x0010 + TVS_EX_AUTOHSCROLL = 0x0020 + TVS_EX_FADEINOUTEXPANDOS = 0x0040 + TVS_EX_PARTIALCHECKBOXES = 0x0080 + TVS_EX_EXCLUSIONCHECKBOXES = 0x0100 + TVS_EX_DIMMEDCHECKBOXES = 0x0200 + TVS_EX_DRAWIMAGEASYNC = 0x0400 +) + +const ( + TVIF_TEXT = 0x0001 + TVIF_IMAGE = 0x0002 + TVIF_PARAM = 0x0004 + TVIF_STATE = 0x0008 + TVIF_HANDLE = 0x0010 + TVIF_SELECTEDIMAGE = 0x0020 + TVIF_CHILDREN = 0x0040 + TVIF_INTEGRAL = 0x0080 + TVIF_STATEEX = 0x0100 + TVIF_EXPANDEDIMAGE = 0x0200 +) + +const ( + TVIS_SELECTED = 0x0002 + TVIS_CUT = 0x0004 + TVIS_DROPHILITED = 0x0008 + TVIS_BOLD = 0x0010 + TVIS_EXPANDED = 0x0020 + TVIS_EXPANDEDONCE = 0x0040 + TVIS_EXPANDPARTIAL = 0x0080 + TVIS_OVERLAYMASK = 0x0F00 + TVIS_STATEIMAGEMASK = 0xF000 + TVIS_USERMASK = 0xF000 +) + +const ( + TVIS_EX_FLAT = 0x0001 + TVIS_EX_DISABLED = 0x0002 + TVIS_EX_ALL = 0x0002 +) + +const ( + TVI_ROOT = ^HTREEITEM(0xffff) + TVI_FIRST = ^HTREEITEM(0xfffe) + TVI_LAST = ^HTREEITEM(0xfffd) + TVI_SORT = ^HTREEITEM(0xfffc) +) + +// TVM_EXPAND action flags +const ( + TVE_COLLAPSE = 0x0001 + TVE_EXPAND = 0x0002 + TVE_TOGGLE = 0x0003 + TVE_EXPANDPARTIAL = 0x4000 + TVE_COLLAPSERESET = 0x8000 +) + +const ( + TVGN_CARET = 9 +) + +// TreeView messages +const ( + TV_FIRST = 0x1100 + + TVM_INSERTITEM = TV_FIRST + 50 + TVM_DELETEITEM = TV_FIRST + 1 + TVM_EXPAND = TV_FIRST + 2 + TVM_GETITEMRECT = TV_FIRST + 4 + TVM_GETCOUNT = TV_FIRST + 5 + TVM_GETINDENT = TV_FIRST + 6 + TVM_SETINDENT = TV_FIRST + 7 + TVM_GETIMAGELIST = TV_FIRST + 8 + TVM_SETIMAGELIST = TV_FIRST + 9 + TVM_GETNEXTITEM = TV_FIRST + 10 + TVM_SELECTITEM = TV_FIRST + 11 + TVM_GETITEM = TV_FIRST + 62 + TVM_SETITEM = TV_FIRST + 63 + TVM_EDITLABEL = TV_FIRST + 65 + TVM_GETEDITCONTROL = TV_FIRST + 15 + TVM_GETVISIBLECOUNT = TV_FIRST + 16 + TVM_HITTEST = TV_FIRST + 17 + TVM_CREATEDRAGIMAGE = TV_FIRST + 18 + TVM_SORTCHILDREN = TV_FIRST + 19 + TVM_ENSUREVISIBLE = TV_FIRST + 20 + TVM_SORTCHILDRENCB = TV_FIRST + 21 + TVM_ENDEDITLABELNOW = TV_FIRST + 22 + TVM_GETISEARCHSTRING = TV_FIRST + 64 + TVM_SETTOOLTIPS = TV_FIRST + 24 + TVM_GETTOOLTIPS = TV_FIRST + 25 + TVM_SETINSERTMARK = TV_FIRST + 26 + TVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TVM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT + TVM_SETITEMHEIGHT = TV_FIRST + 27 + TVM_GETITEMHEIGHT = TV_FIRST + 28 + TVM_SETBKCOLOR = TV_FIRST + 29 + TVM_SETTEXTCOLOR = TV_FIRST + 30 + TVM_GETBKCOLOR = TV_FIRST + 31 + TVM_GETTEXTCOLOR = TV_FIRST + 32 + TVM_SETSCROLLTIME = TV_FIRST + 33 + TVM_GETSCROLLTIME = TV_FIRST + 34 + TVM_SETINSERTMARKCOLOR = TV_FIRST + 37 + TVM_GETINSERTMARKCOLOR = TV_FIRST + 38 + TVM_GETITEMSTATE = TV_FIRST + 39 + TVM_SETLINECOLOR = TV_FIRST + 40 + TVM_GETLINECOLOR = TV_FIRST + 41 + TVM_MAPACCIDTOHTREEITEM = TV_FIRST + 42 + TVM_MAPHTREEITEMTOACCID = TV_FIRST + 43 + TVM_SETEXTENDEDSTYLE = TV_FIRST + 44 + TVM_GETEXTENDEDSTYLE = TV_FIRST + 45 + TVM_SETAUTOSCROLLINFO = TV_FIRST + 59 +) + +// TreeView notifications +const ( + TVN_FIRST = ^uint32(399) + + TVN_SELCHANGING = TVN_FIRST - 50 + TVN_SELCHANGED = TVN_FIRST - 51 + TVN_GETDISPINFO = TVN_FIRST - 52 + TVN_ITEMEXPANDING = TVN_FIRST - 54 + TVN_ITEMEXPANDED = TVN_FIRST - 55 + TVN_BEGINDRAG = TVN_FIRST - 56 + TVN_BEGINRDRAG = TVN_FIRST - 57 + TVN_DELETEITEM = TVN_FIRST - 58 + TVN_BEGINLABELEDIT = TVN_FIRST - 59 + TVN_ENDLABELEDIT = TVN_FIRST - 60 + TVN_KEYDOWN = TVN_FIRST - 12 + TVN_GETINFOTIP = TVN_FIRST - 14 + TVN_SINGLEEXPAND = TVN_FIRST - 15 + TVN_ITEMCHANGING = TVN_FIRST - 17 + TVN_ITEMCHANGED = TVN_FIRST - 19 + TVN_ASYNCDRAW = TVN_FIRST - 20 +) + +// TreeView hit test constants +const ( + TVHT_NOWHERE = 1 + TVHT_ONITEMICON = 2 + TVHT_ONITEMLABEL = 4 + TVHT_ONITEM = TVHT_ONITEMICON | TVHT_ONITEMLABEL | TVHT_ONITEMSTATEICON + TVHT_ONITEMINDENT = 8 + TVHT_ONITEMBUTTON = 16 + TVHT_ONITEMRIGHT = 32 + TVHT_ONITEMSTATEICON = 64 + TVHT_ABOVE = 256 + TVHT_BELOW = 512 + TVHT_TORIGHT = 1024 + TVHT_TOLEFT = 2048 +) + +type HTREEITEM HANDLE + +type TVITEM struct { + Mask uint32 + HItem HTREEITEM + State uint32 + StateMask uint32 + PszText uintptr + CchTextMax int32 + IImage int32 + ISelectedImage int32 + CChildren int32 + LParam uintptr +} + +/*type TVITEMEX struct { + mask UINT + hItem HTREEITEM + state UINT + stateMask UINT + pszText LPWSTR + cchTextMax int + iImage int + iSelectedImage int + cChildren int + lParam LPARAM + iIntegral int + uStateEx UINT + hwnd HWND + iExpandedImage int +}*/ + +type TVINSERTSTRUCT struct { + HParent HTREEITEM + HInsertAfter HTREEITEM + Item TVITEM + // itemex TVITEMEX +} + +type NMTREEVIEW struct { + Hdr NMHDR + Action uint32 + ItemOld TVITEM + ItemNew TVITEM + PtDrag POINT +} + +type NMTVDISPINFO struct { + Hdr NMHDR + Item TVITEM +} + +type NMTVKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +type TVHITTESTINFO struct { + Pt POINT + Flags uint32 + HItem HTREEITEM +} + +// TabPage support + +const TCM_FIRST = 0x1300 +const TCN_FIRST = -550 + +const ( + TCS_SCROLLOPPOSITE = 0x0001 + TCS_BOTTOM = 0x0002 + TCS_RIGHT = 0x0002 + TCS_MULTISELECT = 0x0004 + TCS_FLATBUTTONS = 0x0008 + TCS_FORCEICONLEFT = 0x0010 + TCS_FORCELABELLEFT = 0x0020 + TCS_HOTTRACK = 0x0040 + TCS_VERTICAL = 0x0080 + TCS_TABS = 0x0000 + TCS_BUTTONS = 0x0100 + TCS_SINGLELINE = 0x0000 + TCS_MULTILINE = 0x0200 + TCS_RIGHTJUSTIFY = 0x0000 + TCS_FIXEDWIDTH = 0x0400 + TCS_RAGGEDRIGHT = 0x0800 + TCS_FOCUSONBUTTONDOWN = 0x1000 + TCS_OWNERDRAWFIXED = 0x2000 + TCS_TOOLTIPS = 0x4000 + TCS_FOCUSNEVER = 0x8000 +) + +const ( + TCS_EX_FLATSEPARATORS = 0x00000001 + TCS_EX_REGISTERDROP = 0x00000002 +) + +const ( + TCM_GETIMAGELIST = TCM_FIRST + 2 + TCM_SETIMAGELIST = TCM_FIRST + 3 + TCM_GETITEMCOUNT = TCM_FIRST + 4 + TCM_GETITEM = TCM_FIRST + 60 + TCM_SETITEM = TCM_FIRST + 61 + TCM_INSERTITEM = TCM_FIRST + 62 + TCM_DELETEITEM = TCM_FIRST + 8 + TCM_DELETEALLITEMS = TCM_FIRST + 9 + TCM_GETITEMRECT = TCM_FIRST + 10 + TCM_GETCURSEL = TCM_FIRST + 11 + TCM_SETCURSEL = TCM_FIRST + 12 + TCM_HITTEST = TCM_FIRST + 13 + TCM_SETITEMEXTRA = TCM_FIRST + 14 + TCM_ADJUSTRECT = TCM_FIRST + 40 + TCM_SETITEMSIZE = TCM_FIRST + 41 + TCM_REMOVEIMAGE = TCM_FIRST + 42 + TCM_SETPADDING = TCM_FIRST + 43 + TCM_GETROWCOUNT = TCM_FIRST + 44 + TCM_GETTOOLTIPS = TCM_FIRST + 45 + TCM_SETTOOLTIPS = TCM_FIRST + 46 + TCM_GETCURFOCUS = TCM_FIRST + 47 + TCM_SETCURFOCUS = TCM_FIRST + 48 + TCM_SETMINTABWIDTH = TCM_FIRST + 49 + TCM_DESELECTALL = TCM_FIRST + 50 + TCM_HIGHLIGHTITEM = TCM_FIRST + 51 + TCM_SETEXTENDEDSTYLE = TCM_FIRST + 52 + TCM_GETEXTENDEDSTYLE = TCM_FIRST + 53 + TCM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TCM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT +) + +const ( + TCIF_TEXT = 0x0001 + TCIF_IMAGE = 0x0002 + TCIF_RTLREADING = 0x0004 + TCIF_PARAM = 0x0008 + TCIF_STATE = 0x0010 +) + +const ( + TCIS_BUTTONPRESSED = 0x0001 + TCIS_HIGHLIGHTED = 0x0002 +) + +const ( + TCHT_NOWHERE = 0x0001 + TCHT_ONITEMICON = 0x0002 + TCHT_ONITEMLABEL = 0x0004 + TCHT_ONITEM = TCHT_ONITEMICON | TCHT_ONITEMLABEL +) + +const ( + TCN_KEYDOWN = TCN_FIRST - 0 + TCN_SELCHANGE = TCN_FIRST - 1 + TCN_SELCHANGING = TCN_FIRST - 2 + TCN_GETOBJECT = TCN_FIRST - 3 + TCN_FOCUSCHANGE = TCN_FIRST - 4 +) + +type TCITEMHEADER struct { + Mask uint32 + LpReserved1 uint32 + LpReserved2 uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 +} + +type TCITEM struct { + Mask uint32 + DwState uint32 + DwStateMask uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 + LParam uintptr +} + +type TCHITTESTINFO struct { + Pt POINT + flags uint32 +} + +type NMTCKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +// Menu support constants + +// Constants for MENUITEMINFO.fMask +const ( + MIIM_STATE = 1 + MIIM_ID = 2 + MIIM_SUBMENU = 4 + MIIM_CHECKMARKS = 8 + MIIM_TYPE = 16 + MIIM_DATA = 32 + MIIM_STRING = 64 + MIIM_BITMAP = 128 + MIIM_FTYPE = 256 +) + +// Constants for MENUITEMINFO.fType +const ( + MFT_BITMAP = 4 + MFT_MENUBARBREAK = 32 + MFT_MENUBREAK = 64 + MFT_OWNERDRAW = 256 + MFT_RADIOCHECK = 512 + MFT_RIGHTJUSTIFY = 0x4000 + MFT_SEPARATOR = 0x800 + MFT_RIGHTORDER = 0x2000 + MFT_STRING = 0 +) + +// Constants for MENUITEMINFO.fState +const ( + MFS_CHECKED = 8 + MFS_DEFAULT = 4096 + MFS_DISABLED = 3 + MFS_ENABLED = 0 + MFS_GRAYED = 3 + MFS_HILITE = 128 + MFS_UNCHECKED = 0 + MFS_UNHILITE = 0 +) + +// Constants for MENUITEMINFO.hbmp* +const ( + HBMMENU_CALLBACK = -1 + HBMMENU_SYSTEM = 1 + HBMMENU_MBAR_RESTORE = 2 + HBMMENU_MBAR_MINIMIZE = 3 + HBMMENU_MBAR_CLOSE = 5 + HBMMENU_MBAR_CLOSE_D = 6 + HBMMENU_MBAR_MINIMIZE_D = 7 + HBMMENU_POPUP_CLOSE = 8 + HBMMENU_POPUP_RESTORE = 9 + HBMMENU_POPUP_MAXIMIZE = 10 + HBMMENU_POPUP_MINIMIZE = 11 +) + +// MENUINFO mask constants +const ( + MIM_APPLYTOSUBMENUS = 0x80000000 + MIM_BACKGROUND = 0x00000002 + MIM_HELPID = 0x00000004 + MIM_MAXHEIGHT = 0x00000001 + MIM_MENUDATA = 0x00000008 + MIM_STYLE = 0x00000010 +) + +// MENUINFO style constants +const ( + MNS_AUTODISMISS = 0x10000000 + MNS_CHECKORBMP = 0x04000000 + MNS_DRAGDROP = 0x20000000 + MNS_MODELESS = 0x40000000 + MNS_NOCHECK = 0x80000000 + MNS_NOTIFYBYPOS = 0x08000000 +) + +const ( + MF_BYCOMMAND = 0x00000000 + MF_BYPOSITION = 0x00000400 + MF_BITMAP = 0x00000004 + MF_CHECKED = 0x00000008 + MF_DISABLED = 0x00000002 + MF_ENABLED = 0x00000000 + MF_GRAYED = 0x00000001 + MF_MENUBARBREAK = 0x00000020 + MF_MENUBREAK = 0x00000040 + MF_OWNERDRAW = 0x00000100 + MF_POPUP = 0x00000010 + MF_SEPARATOR = 0x00000800 + MF_STRING = 0x00000000 + MF_UNCHECKED = 0x00000000 +) + +type MENUITEMINFO struct { + CbSize uint32 + FMask uint32 + FType uint32 + FState uint32 + WID uint32 + HSubMenu HMENU + HbmpChecked HBITMAP + HbmpUnchecked HBITMAP + DwItemData uintptr + DwTypeData *uint16 + Cch uint32 + HbmpItem HBITMAP +} + +type MENUINFO struct { + CbSize uint32 + FMask uint32 + DwStyle uint32 + CyMax uint32 + HbrBack HBRUSH + DwContextHelpID uint32 + DwMenuData uintptr +} + +// UI state constants +const ( + UIS_SET = 1 + UIS_CLEAR = 2 + UIS_INITIALIZE = 3 +) + +// UI state constants +const ( + UISF_HIDEFOCUS = 0x1 + UISF_HIDEACCEL = 0x2 + UISF_ACTIVE = 0x4 +) + +// Virtual key codes +const ( + VK_LBUTTON = 1 + VK_RBUTTON = 2 + VK_CANCEL = 3 + VK_MBUTTON = 4 + VK_XBUTTON1 = 5 + VK_XBUTTON2 = 6 + VK_BACK = 8 + VK_TAB = 9 + VK_CLEAR = 12 + VK_RETURN = 13 + VK_SHIFT = 16 + VK_CONTROL = 17 + VK_MENU = 18 + VK_PAUSE = 19 + VK_CAPITAL = 20 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 32 + VK_PRIOR = 33 + VK_NEXT = 34 + VK_END = 35 + VK_HOME = 36 + VK_LEFT = 37 + VK_UP = 38 + VK_RIGHT = 39 + VK_DOWN = 40 + VK_SELECT = 41 + VK_PRINT = 42 + VK_EXECUTE = 43 + VK_SNAPSHOT = 44 + VK_INSERT = 45 + VK_DELETE = 46 + VK_HELP = 47 + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_102 = 0xE2 + VK_PROCESSKEY = 0xE5 + VK_PACKET = 0xE7 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +) + +// ScrollBar constants +const ( + SB_HORZ = 0 + SB_VERT = 1 + SB_CTL = 2 + SB_BOTH = 3 +) + +// ScrollBar commands +const ( + SB_LINEUP = 0 + SB_LINELEFT = 0 + SB_LINEDOWN = 1 + SB_LINERIGHT = 1 + SB_PAGEUP = 2 + SB_PAGELEFT = 2 + SB_PAGEDOWN = 3 + SB_PAGERIGHT = 3 + SB_THUMBPOSITION = 4 + SB_THUMBTRACK = 5 + SB_TOP = 6 + SB_LEFT = 6 + SB_BOTTOM = 7 + SB_RIGHT = 7 + SB_ENDSCROLL = 8 +) + +// [Get|Set]ScrollInfo mask constants +const ( + SIF_RANGE = 1 + SIF_PAGE = 2 + SIF_POS = 4 + SIF_DISABLENOSCROLL = 8 + SIF_TRACKPOS = 16 + SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS +) + +const AC_SRC_OVER = 0 +const AC_SRC_ALPHA = 1 + +const ULW_COLORKEY = 1 +const ULW_ALPHA = 2 +const ULW_OPAQUE = 4 +const ULW_EX_NORESIZE = 8 + +// RedrawWindow flags +const ( + RDW_INVALIDATE = 0x0001 + RDW_INTERNALPAINT = 0x0002 + RDW_ERASE = 0x0004 + RDW_VALIDATE = 0x0008 + RDW_NOINTERNALPAINT = 0x0010 + RDW_NOERASE = 0x0020 + RDW_NOCHILDREN = 0x0040 + RDW_ALLCHILDREN = 0x0080 + RDW_UPDATENOW = 0x0100 + RDW_ERASENOW = 0x0200 + RDW_FRAME = 0x0400 + RDW_NOFRAME = 0x0800 +) diff --git a/v3/pkg/w32/consts.go b/v3/pkg/w32/consts.go new file mode 100644 index 000000000..b8cdf0358 --- /dev/null +++ b/v3/pkg/w32/consts.go @@ -0,0 +1,102 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" + "strconv" + "syscall" + "unsafe" +) + +var ( + modwingdi = syscall.NewLazyDLL("gdi32.dll") + procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush") +) +var ( + kernel32 = syscall.NewLazyDLL("kernel32") + kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc") + kernelGlobalFree = kernel32.NewProc("GlobalFree") + kernelGlobalLock = kernel32.NewProc("GlobalLock") + kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock") + kernelLstrcpy = kernel32.NewProc("lstrcpyW") +) +var ( + modBranding = syscall.NewLazyDLL("winbrand.dll") + brandingFormatString = modBranding.NewProc("BrandingFormatString") +) + +var windowsVersion, _ = GetWindowsVersionInfo() + +func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return windowsVersion.Major >= major && + windowsVersion.Minor >= minor && + windowsVersion.Build >= buildNumber +} + +type WindowsVersionInfo struct { + Major int + Minor int + Build int + DisplayVersion string +} + +func (w *WindowsVersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d (%s)", w.Major, w.Minor, w.Build, w.DisplayVersion) +} + +func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber +} + +func GetBranding() string { + windowsLong := MustStringToUTF16Ptr("%WINDOWS_LONG%\x00") + ret, _, _ := brandingFormatString.Call( + uintptr(unsafe.Pointer(windowsLong)), + ) + return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ret))) +} + +func GetWindowsVersionInfo() (*WindowsVersionInfo, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + + return &WindowsVersionInfo{ + Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"), + Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"), + Build: regStringKeyAsInt(key, "CurrentBuildNumber"), + DisplayVersion: regKeyAsString(key, "DisplayVersion"), + }, nil +} + +func regDWORDKeyAsInt(key registry.Key, name string) int { + result, _, err := key.GetIntegerValue(name) + if err != nil { + return -1 + } + return int(result) +} + +func regStringKeyAsInt(key registry.Key, name string) int { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return -1 + } + result, err := strconv.Atoi(resultStr) + if err != nil { + return -1 + } + return result +} + +func regKeyAsString(key registry.Key, name string) string { + resultStr, _, err := key.GetStringValue(name) + if err != nil { + return "" + } + return resultStr +} diff --git a/v3/pkg/w32/dialogs.go b/v3/pkg/w32/dialogs.go new file mode 100644 index 000000000..d327187c0 --- /dev/null +++ b/v3/pkg/w32/dialogs.go @@ -0,0 +1,28 @@ +//go:build windows + +package w32 + +import ( + "unsafe" +) + +func MessageBoxWithIcon(hwnd HWND, text *uint16, caption *uint16, iconID int, flags uint32) (int32, error) { + + params := MSGBOXPARAMS{ + cbSize: uint32(unsafe.Sizeof(MSGBOXPARAMS{})), + hwndOwner: hwnd, + hInstance: GetApplicationHandle(), + lpszText: text, + lpszCaption: caption, + dwStyle: flags, + lpszIcon: (*uint16)(unsafe.Pointer(uintptr(iconID))), + } + + r, _, err := procMessageBoxIndirect.Call( + uintptr(unsafe.Pointer(¶ms)), + ) + if r == 0 { + return 0, err + } + return int32(r), nil +} diff --git a/v3/pkg/w32/dwmapi.go b/v3/pkg/w32/dwmapi.go new file mode 100644 index 000000000..1b911efc3 --- /dev/null +++ b/v3/pkg/w32/dwmapi.go @@ -0,0 +1,46 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + moddwmapi = syscall.NewLazyDLL("dwmapi.dll") + + procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") + procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute") + procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea") +) + +func DwmSetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) HRESULT { + ret, _, _ := procDwmSetWindowAttribute.Call( + hwnd, + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + return HRESULT(ret) +} + +func DwmGetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) HRESULT { + ret, _, _ := procDwmGetWindowAttribute.Call( + hwnd, + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + return HRESULT(ret) +} + +func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error { + ret, _, _ := procDwmExtendFrameIntoClientArea.Call( + hwnd, + uintptr(unsafe.Pointer(margins))) + + if ret != 0 { + return syscall.GetLastError() + } + + return nil +} diff --git a/v3/pkg/w32/gdi32.go b/v3/pkg/w32/gdi32.go new file mode 100644 index 000000000..04d11ca14 --- /dev/null +++ b/v3/pkg/w32/gdi32.go @@ -0,0 +1,581 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modgdi32 = syscall.NewLazyDLL("gdi32.dll") + + procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps") + procDeleteObject = modgdi32.NewProc("DeleteObject") + procCreateFontIndirect = modgdi32.NewProc("CreateFontIndirectW") + procAbortDoc = modgdi32.NewProc("AbortDoc") + procBitBlt = modgdi32.NewProc("BitBlt") + procPatBlt = modgdi32.NewProc("PatBlt") + procCloseEnhMetaFile = modgdi32.NewProc("CloseEnhMetaFile") + procCopyEnhMetaFile = modgdi32.NewProc("CopyEnhMetaFileW") + procCreateBrushIndirect = modgdi32.NewProc("CreateBrushIndirect") + procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC") + procCreateDC = modgdi32.NewProc("CreateDCW") + procCreateDIBSection = modgdi32.NewProc("CreateDIBSection") + procCreateEnhMetaFile = modgdi32.NewProc("CreateEnhMetaFileW") + procCreateIC = modgdi32.NewProc("CreateICW") + procDeleteDC = modgdi32.NewProc("DeleteDC") + procDeleteEnhMetaFile = modgdi32.NewProc("DeleteEnhMetaFile") + procEllipse = modgdi32.NewProc("Ellipse") + procEndDoc = modgdi32.NewProc("EndDoc") + procEndPage = modgdi32.NewProc("EndPage") + procExtCreatePen = modgdi32.NewProc("ExtCreatePen") + procGetEnhMetaFile = modgdi32.NewProc("GetEnhMetaFileW") + procGetEnhMetaFileHeader = modgdi32.NewProc("GetEnhMetaFileHeader") + procGetObject = modgdi32.NewProc("GetObjectW") + procGetStockObject = modgdi32.NewProc("GetStockObject") + procGetTextExtentExPoint = modgdi32.NewProc("GetTextExtentExPointW") + procGetTextExtentPoint32 = modgdi32.NewProc("GetTextExtentPoint32W") + procGetTextMetrics = modgdi32.NewProc("GetTextMetricsW") + procLineTo = modgdi32.NewProc("LineTo") + procMoveToEx = modgdi32.NewProc("MoveToEx") + procPlayEnhMetaFile = modgdi32.NewProc("PlayEnhMetaFile") + procRectangle = modgdi32.NewProc("Rectangle") + procResetDC = modgdi32.NewProc("ResetDCW") + procSelectObject = modgdi32.NewProc("SelectObject") + procSetBkMode = modgdi32.NewProc("SetBkMode") + procSetBrushOrgEx = modgdi32.NewProc("SetBrushOrgEx") + procSetStretchBltMode = modgdi32.NewProc("SetStretchBltMode") + procSetTextColor = modgdi32.NewProc("SetTextColor") + procSetBkColor = modgdi32.NewProc("SetBkColor") + procStartDoc = modgdi32.NewProc("StartDocW") + procStartPage = modgdi32.NewProc("StartPage") + procStretchBlt = modgdi32.NewProc("StretchBlt") + procSetDIBitsToDevice = modgdi32.NewProc("SetDIBitsToDevice") + procChoosePixelFormat = modgdi32.NewProc("ChoosePixelFormat") + procDescribePixelFormat = modgdi32.NewProc("DescribePixelFormat") + procGetEnhMetaFilePixelFormat = modgdi32.NewProc("GetEnhMetaFilePixelFormat") + procGetPixelFormat = modgdi32.NewProc("GetPixelFormat") + procSetPixelFormat = modgdi32.NewProc("SetPixelFormat") + procSwapBuffers = modgdi32.NewProc("SwapBuffers") + procSaveDC = modgdi32.NewProc("SaveDC") + procRestoreDC = modgdi32.NewProc("RestoreDC") + procSelectClipRgn = modgdi32.NewProc("SelectClipRgn") + procExcludeClipRect = modgdi32.NewProc("ExcludeClipRect") + procExtTextOut = modgdi32.NewProc("ExtTextOutW") +) + +func GetDeviceCaps(hdc HDC, index int) int { + ret, _, _ := procGetDeviceCaps.Call( + uintptr(hdc), + uintptr(index)) + + return int(ret) +} + +func DeleteObject(hObject HGDIOBJ) bool { + ret, _, _ := procDeleteObject.Call( + uintptr(hObject)) + + return ret != 0 +} + +func CreateFontIndirect(logFont *LOGFONT) HFONT { + ret, _, _ := procCreateFontIndirect.Call( + uintptr(unsafe.Pointer(logFont))) + + return HFONT(ret) +} + +func AbortDoc(hdc HDC) int { + ret, _, _ := procAbortDoc.Call( + uintptr(hdc)) + + return int(ret) +} + +func BitBlt(hdcDest HDC, nXDest, nYDest, nWidth, nHeight int, hdcSrc HDC, nXSrc, nYSrc int, dwRop uint) { + ret, _, _ := procBitBlt.Call( + uintptr(hdcDest), + uintptr(nXDest), + uintptr(nYDest), + uintptr(nWidth), + uintptr(nHeight), + uintptr(hdcSrc), + uintptr(nXSrc), + uintptr(nYSrc), + uintptr(dwRop)) + + if ret == 0 { + panic("BitBlt failed") + } +} + +func PatBlt(hdc HDC, nXLeft, nYLeft, nWidth, nHeight int, dwRop uint) { + ret, _, _ := procPatBlt.Call( + uintptr(hdc), + uintptr(nXLeft), + uintptr(nYLeft), + uintptr(nWidth), + uintptr(nHeight), + uintptr(dwRop)) + + if ret == 0 { + panic("PatBlt failed") + } +} + +func CloseEnhMetaFile(hdc HDC) HENHMETAFILE { + ret, _, _ := procCloseEnhMetaFile.Call( + uintptr(hdc)) + + return HENHMETAFILE(ret) +} + +func CopyEnhMetaFile(hemfSrc HENHMETAFILE, lpszFile *uint16) HENHMETAFILE { + ret, _, _ := procCopyEnhMetaFile.Call( + uintptr(hemfSrc), + uintptr(unsafe.Pointer(lpszFile))) + + return HENHMETAFILE(ret) +} + +func CreateBrushIndirect(lplb *LOGBRUSH) HBRUSH { + ret, _, _ := procCreateBrushIndirect.Call( + uintptr(unsafe.Pointer(lplb))) + + return HBRUSH(ret) +} + +func CreateCompatibleDC(hdc HDC) HDC { + ret, _, _ := procCreateCompatibleDC.Call( + uintptr(hdc)) + + if ret == 0 { + panic("Create compatible DC failed") + } + + return HDC(ret) +} + +func CreateDC(lpszDriver, lpszDevice, lpszOutput *uint16, lpInitData *DEVMODE) HDC { + ret, _, _ := procCreateDC.Call( + uintptr(unsafe.Pointer(lpszDriver)), + uintptr(unsafe.Pointer(lpszDevice)), + uintptr(unsafe.Pointer(lpszOutput)), + uintptr(unsafe.Pointer(lpInitData))) + + return HDC(ret) +} + +func CreateDIBSection(hdc HDC, pbmi *BITMAPINFO, iUsage uint, ppvBits *unsafe.Pointer, hSection HANDLE, dwOffset uint) HBITMAP { + ret, _, _ := procCreateDIBSection.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(pbmi)), + uintptr(iUsage), + uintptr(unsafe.Pointer(ppvBits)), + uintptr(hSection), + uintptr(dwOffset)) + + return HBITMAP(ret) +} + +func CreateEnhMetaFile(hdcRef HDC, lpFilename *uint16, lpRect *RECT, lpDescription *uint16) HDC { + ret, _, _ := procCreateEnhMetaFile.Call( + uintptr(hdcRef), + uintptr(unsafe.Pointer(lpFilename)), + uintptr(unsafe.Pointer(lpRect)), + uintptr(unsafe.Pointer(lpDescription))) + + return HDC(ret) +} + +func CreateIC(lpszDriver, lpszDevice, lpszOutput *uint16, lpdvmInit *DEVMODE) HDC { + ret, _, _ := procCreateIC.Call( + uintptr(unsafe.Pointer(lpszDriver)), + uintptr(unsafe.Pointer(lpszDevice)), + uintptr(unsafe.Pointer(lpszOutput)), + uintptr(unsafe.Pointer(lpdvmInit))) + + return HDC(ret) +} + +func DeleteDC(hdc HDC) bool { + ret, _, _ := procDeleteDC.Call( + uintptr(hdc)) + + return ret != 0 +} + +func DeleteEnhMetaFile(hemf HENHMETAFILE) bool { + ret, _, _ := procDeleteEnhMetaFile.Call( + uintptr(hemf)) + + return ret != 0 +} + +func Ellipse(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool { + ret, _, _ := procEllipse.Call( + uintptr(hdc), + uintptr(nLeftRect), + uintptr(nTopRect), + uintptr(nRightRect), + uintptr(nBottomRect)) + + return ret != 0 +} + +func EndDoc(hdc HDC) int { + ret, _, _ := procEndDoc.Call( + uintptr(hdc)) + + return int(ret) +} + +func EndPage(hdc HDC) int { + ret, _, _ := procEndPage.Call( + uintptr(hdc)) + + return int(ret) +} + +func ExtCreatePen(dwPenStyle, dwWidth uint, lplb *LOGBRUSH, dwStyleCount uint, lpStyle *uint) HPEN { + ret, _, _ := procExtCreatePen.Call( + uintptr(dwPenStyle), + uintptr(dwWidth), + uintptr(unsafe.Pointer(lplb)), + uintptr(dwStyleCount), + uintptr(unsafe.Pointer(lpStyle))) + + return HPEN(ret) +} + +func GetEnhMetaFile(lpszMetaFile *uint16) HENHMETAFILE { + ret, _, _ := procGetEnhMetaFile.Call( + uintptr(unsafe.Pointer(lpszMetaFile))) + + return HENHMETAFILE(ret) +} + +func GetEnhMetaFileHeader(hemf HENHMETAFILE, cbBuffer uint, lpemh *ENHMETAHEADER) uint { + ret, _, _ := procGetEnhMetaFileHeader.Call( + uintptr(hemf), + uintptr(cbBuffer), + uintptr(unsafe.Pointer(lpemh))) + + return uint(ret) +} + +func GetObject(hgdiobj HGDIOBJ, cbBuffer uintptr, lpvObject unsafe.Pointer) int { + ret, _, _ := procGetObject.Call( + uintptr(hgdiobj), + uintptr(cbBuffer), + uintptr(lpvObject)) + + return int(ret) +} + +func GetStockObject(fnObject int) HGDIOBJ { + ret, _, _ := procGetDeviceCaps.Call( + uintptr(fnObject)) + + return HGDIOBJ(ret) +} + +func GetTextExtentExPoint(hdc HDC, lpszStr *uint16, cchString, nMaxExtent int, lpnFit, alpDx *int, lpSize *SIZE) bool { + ret, _, _ := procGetTextExtentExPoint.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpszStr)), + uintptr(cchString), + uintptr(nMaxExtent), + uintptr(unsafe.Pointer(lpnFit)), + uintptr(unsafe.Pointer(alpDx)), + uintptr(unsafe.Pointer(lpSize))) + + return ret != 0 +} + +func GetTextExtentPoint32(hdc HDC, lpString *uint16, c int, lpSize *SIZE) bool { + ret, _, _ := procGetTextExtentPoint32.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpString)), + uintptr(c), + uintptr(unsafe.Pointer(lpSize))) + + return ret != 0 +} + +func GetTextMetrics(hdc HDC, lptm *TEXTMETRIC) bool { + ret, _, _ := procGetTextMetrics.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lptm))) + + return ret != 0 +} + +func LineTo(hdc HDC, nXEnd, nYEnd int32) bool { + ret, _, _ := procLineTo.Call( + uintptr(hdc), + uintptr(nXEnd), + uintptr(nYEnd)) + + return ret != 0 +} + +func MoveToEx(hdc HDC, x, y int, lpPoint *POINT) bool { + ret, _, _ := procMoveToEx.Call( + uintptr(hdc), + uintptr(x), + uintptr(y), + uintptr(unsafe.Pointer(lpPoint))) + + return ret != 0 +} + +func PlayEnhMetaFile(hdc HDC, hemf HENHMETAFILE, lpRect *RECT) bool { + ret, _, _ := procPlayEnhMetaFile.Call( + uintptr(hdc), + uintptr(hemf), + uintptr(unsafe.Pointer(lpRect))) + + return ret != 0 +} + +func Rectangle(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool { + ret, _, _ := procRectangle.Call( + uintptr(hdc), + uintptr(nLeftRect), + uintptr(nTopRect), + uintptr(nRightRect), + uintptr(nBottomRect)) + + return ret != 0 +} + +func ResetDC(hdc HDC, lpInitData *DEVMODE) HDC { + ret, _, _ := procResetDC.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpInitData))) + + return HDC(ret) +} + +func SelectObject(hdc HDC, hgdiobj HGDIOBJ) HGDIOBJ { + ret, _, _ := procSelectObject.Call( + uintptr(hdc), + uintptr(hgdiobj)) + + if ret == 0 { + panic("SelectObject failed") + } + + return HGDIOBJ(ret) +} + +func SetBkMode(hdc HDC, iBkMode int) int { + ret, _, _ := procSetBkMode.Call( + uintptr(hdc), + uintptr(iBkMode)) + + if ret == 0 { + panic("SetBkMode failed") + } + + return int(ret) +} + +func SetBrushOrgEx(hdc HDC, nXOrg, nYOrg int, lppt *POINT) bool { + ret, _, _ := procSetBrushOrgEx.Call( + uintptr(hdc), + uintptr(nXOrg), + uintptr(nYOrg), + uintptr(unsafe.Pointer(lppt))) + + return ret != 0 +} + +func SetStretchBltMode(hdc HDC, iStretchMode int) int { + ret, _, _ := procSetStretchBltMode.Call( + uintptr(hdc), + uintptr(iStretchMode)) + + return int(ret) +} + +func SetTextColor(hdc HDC, crColor COLORREF) COLORREF { + ret, _, _ := procSetTextColor.Call( + uintptr(hdc), + uintptr(crColor)) + + if ret == CLR_INVALID { + panic("SetTextColor failed") + } + + return COLORREF(ret) +} + +func SetBkColor(hdc HDC, crColor COLORREF) COLORREF { + ret, _, _ := procSetBkColor.Call( + uintptr(hdc), + uintptr(crColor)) + + if ret == CLR_INVALID { + panic("SetBkColor failed") + } + + return COLORREF(ret) +} + +func StartDoc(hdc HDC, lpdi *DOCINFO) int { + ret, _, _ := procStartDoc.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(lpdi))) + + return int(ret) +} + +func StartPage(hdc HDC) int { + ret, _, _ := procStartPage.Call( + uintptr(hdc)) + + return int(ret) +} + +func StretchBlt(hdcDest HDC, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest int, hdcSrc HDC, nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc int, dwRop uint) { + ret, _, _ := procStretchBlt.Call( + uintptr(hdcDest), + uintptr(nXOriginDest), + uintptr(nYOriginDest), + uintptr(nWidthDest), + uintptr(nHeightDest), + uintptr(hdcSrc), + uintptr(nXOriginSrc), + uintptr(nYOriginSrc), + uintptr(nWidthSrc), + uintptr(nHeightSrc), + uintptr(dwRop)) + + if ret == 0 { + panic("StretchBlt failed") + } +} + +func SetDIBitsToDevice(hdc HDC, xDest, yDest, dwWidth, dwHeight, xSrc, ySrc int, uStartScan, cScanLines uint, lpvBits []byte, lpbmi *BITMAPINFO, fuColorUse uint) int { + ret, _, _ := procSetDIBitsToDevice.Call( + uintptr(hdc), + uintptr(xDest), + uintptr(yDest), + uintptr(dwWidth), + uintptr(dwHeight), + uintptr(xSrc), + uintptr(ySrc), + uintptr(uStartScan), + uintptr(cScanLines), + uintptr(unsafe.Pointer(&lpvBits[0])), + uintptr(unsafe.Pointer(lpbmi)), + uintptr(fuColorUse)) + + return int(ret) +} + +func ChoosePixelFormat(hdc HDC, pfd *PIXELFORMATDESCRIPTOR) int { + ret, _, _ := procChoosePixelFormat.Call( + uintptr(hdc), + uintptr(unsafe.Pointer(pfd)), + ) + return int(ret) +} + +func DescribePixelFormat(hdc HDC, iPixelFormat int, nBytes uint, pfd *PIXELFORMATDESCRIPTOR) int { + ret, _, _ := procDescribePixelFormat.Call( + uintptr(hdc), + uintptr(iPixelFormat), + uintptr(nBytes), + uintptr(unsafe.Pointer(pfd)), + ) + return int(ret) +} + +func GetEnhMetaFilePixelFormat(hemf HENHMETAFILE, cbBuffer uint32, pfd *PIXELFORMATDESCRIPTOR) uint { + ret, _, _ := procGetEnhMetaFilePixelFormat.Call( + uintptr(hemf), + uintptr(cbBuffer), + uintptr(unsafe.Pointer(pfd)), + ) + return uint(ret) +} + +func GetPixelFormat(hdc HDC) int { + ret, _, _ := procGetPixelFormat.Call( + uintptr(hdc), + ) + return int(ret) +} + +func SetPixelFormat(hdc HDC, iPixelFormat int, pfd *PIXELFORMATDESCRIPTOR) bool { + ret, _, _ := procSetPixelFormat.Call( + uintptr(hdc), + uintptr(iPixelFormat), + uintptr(unsafe.Pointer(pfd)), + ) + return ret == TRUE +} + +func SwapBuffers(hdc HDC) bool { + ret, _, _ := procSwapBuffers.Call(uintptr(hdc)) + return ret == TRUE +} + +func SaveDC(hdc HDC) int { + ret, _, _ := procSaveDC.Call(uintptr(hdc)) + return int(ret) +} + +func RestoreDC(hdc HDC, nSavedDC int) bool { + ret, _, _ := procRestoreDC.Call( + uintptr(hdc), + uintptr(nSavedDC)) + return ret != 0 +} + +func SelectClipRgn(hdc HDC, hrgn HRGN) int { + ret, _, _ := procSelectClipRgn.Call( + uintptr(hdc), + uintptr(hrgn)) + return int(ret) +} + +func ExcludeClipRect(hdc HDC, left, top, right, bottom int32) int { + ret, _, _ := procExcludeClipRect.Call( + uintptr(hdc), + uintptr(left), + uintptr(top), + uintptr(right), + uintptr(bottom)) + return int(ret) +} + +func ExtTextOut(hdc HDC, x, y int32, fuOptions uint32, lprc *RECT, lpString *uint16, cbCount uint32, lpDx *int) bool { + var rectPtr uintptr + if lprc != nil { + rectPtr = uintptr(unsafe.Pointer(lprc)) + } + var dxPtr uintptr + if lpDx != nil { + dxPtr = uintptr(unsafe.Pointer(lpDx)) + } + ret, _, _ := procExtTextOut.Call( + uintptr(hdc), + uintptr(x), + uintptr(y), + uintptr(fuOptions), + rectPtr, + uintptr(unsafe.Pointer(lpString)), + uintptr(cbCount), + dxPtr) + return ret != 0 +} diff --git a/v3/pkg/w32/gdiplus.go b/v3/pkg/w32/gdiplus.go new file mode 100644 index 000000000..2591ed71b --- /dev/null +++ b/v3/pkg/w32/gdiplus.go @@ -0,0 +1,177 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "errors" + "fmt" + "syscall" + "unsafe" +) + +const ( + Ok = 0 + GenericError = 1 + InvalidParameter = 2 + OutOfMemory = 3 + ObjectBusy = 4 + InsufficientBuffer = 5 + NotImplemented = 6 + Win32Error = 7 + WrongState = 8 + Aborted = 9 + FileNotFound = 10 + ValueOverflow = 11 + AccessDenied = 12 + UnknownImageFormat = 13 + FontFamilyNotFound = 14 + FontStyleNotFound = 15 + NotTrueTypeFont = 16 + UnsupportedGdiplusVersion = 17 + GdiplusNotInitialized = 18 + PropertyNotFound = 19 + PropertyNotSupported = 20 + ProfileNotFound = 21 +) + +func GetGpStatus(s int32) string { + switch s { + case Ok: + return "Ok" + case GenericError: + return "GenericError" + case InvalidParameter: + return "InvalidParameter" + case OutOfMemory: + return "OutOfMemory" + case ObjectBusy: + return "ObjectBusy" + case InsufficientBuffer: + return "InsufficientBuffer" + case NotImplemented: + return "NotImplemented" + case Win32Error: + return "Win32Error" + case WrongState: + return "WrongState" + case Aborted: + return "Aborted" + case FileNotFound: + return "FileNotFound" + case ValueOverflow: + return "ValueOverflow" + case AccessDenied: + return "AccessDenied" + case UnknownImageFormat: + return "UnknownImageFormat" + case FontFamilyNotFound: + return "FontFamilyNotFound" + case FontStyleNotFound: + return "FontStyleNotFound" + case NotTrueTypeFont: + return "NotTrueTypeFont" + case UnsupportedGdiplusVersion: + return "UnsupportedGdiplusVersion" + case GdiplusNotInitialized: + return "GdiplusNotInitialized" + case PropertyNotFound: + return "PropertyNotFound" + case PropertyNotSupported: + return "PropertyNotSupported" + case ProfileNotFound: + return "ProfileNotFound" + } + return "Unknown Status Value" +} + +var ( + token uintptr + + modgdiplus = syscall.NewLazyDLL("gdiplus.dll") + + procGdipCreateBitmapFromFile = modgdiplus.NewProc("GdipCreateBitmapFromFile") + procGdipCreateBitmapFromHBITMAP = modgdiplus.NewProc("GdipCreateBitmapFromHBITMAP") + procGdipCreateHBITMAPFromBitmap = modgdiplus.NewProc("GdipCreateHBITMAPFromBitmap") + procGdipCreateBitmapFromResource = modgdiplus.NewProc("GdipCreateBitmapFromResource") + procGdipCreateBitmapFromStream = modgdiplus.NewProc("GdipCreateBitmapFromStream") + procGdipDisposeImage = modgdiplus.NewProc("GdipDisposeImage") + procGdiplusShutdown = modgdiplus.NewProc("GdiplusShutdown") + procGdiplusStartup = modgdiplus.NewProc("GdiplusStartup") +) + +func GdipCreateBitmapFromFile(filename string) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromFile.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(filename))), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromFile failed with status '%s' for file '%s'", GetGpStatus(int32(ret)), filename)) + } + + return bitmap, nil +} + +func GdipCreateBitmapFromResource(instance HINSTANCE, resId *uint16) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromResource.Call( + uintptr(instance), + uintptr(unsafe.Pointer(resId)), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdiCreateBitmapFromResource failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return bitmap, nil +} + +func GdipCreateBitmapFromStream(stream *IStream) (*uintptr, error) { + var bitmap *uintptr + ret, _, _ := procGdipCreateBitmapFromStream.Call( + uintptr(unsafe.Pointer(stream)), + uintptr(unsafe.Pointer(&bitmap))) + + if ret != Ok { + return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromStream failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return bitmap, nil +} + +func GdipCreateHBITMAPFromBitmap(bitmap *uintptr, background uint32) (HBITMAP, error) { + var hbitmap HBITMAP + ret, _, _ := procGdipCreateHBITMAPFromBitmap.Call( + uintptr(unsafe.Pointer(bitmap)), + uintptr(unsafe.Pointer(&hbitmap)), + uintptr(background)) + + if ret != Ok { + return 0, errors.New(fmt.Sprintf("GdipCreateHBITMAPFromBitmap failed with status '%s'", GetGpStatus(int32(ret)))) + } + + return hbitmap, nil +} + +func GdipDisposeImage(image *uintptr) { + procGdipDisposeImage.Call(uintptr(unsafe.Pointer(image))) +} + +func GdiplusShutdown() { + procGdiplusShutdown.Call(token) +} + +func GdiplusStartup(input *GdiplusStartupInput, output *GdiplusStartupOutput) { + ret, _, _ := procGdiplusStartup.Call( + uintptr(unsafe.Pointer(&token)), + uintptr(unsafe.Pointer(input)), + uintptr(unsafe.Pointer(output))) + + if ret != Ok { + panic("GdiplusStartup failed with status " + GetGpStatus(int32(ret))) + } +} diff --git a/v3/pkg/w32/guid.go b/v3/pkg/w32/guid.go new file mode 100644 index 000000000..b6e557f01 --- /dev/null +++ b/v3/pkg/w32/guid.go @@ -0,0 +1,225 @@ +//go:build windows + +package w32 + +// This code has been adapted from: https://github.com/go-ole/go-ole + +/* + +The MIT License (MIT) + +Copyright ยฉ 2013-2017 Yasuhiro Matsumoto, + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the โ€œSoftwareโ€), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +const hextable = "0123456789ABCDEF" +const emptyGUID = "{00000000-0000-0000-0000-000000000000}" + +// GUID is Windows API specific GUID type. +// +// This exists to match Windows GUID type for direct passing for COM. +// Format is in xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx. +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +// NewGUID converts the given string into a globally unique identifier that is +// compliant with the Windows API. +// +// The supplied string may be in any of these formats: +// +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} +// +// The conversion of the supplied string is not case-sensitive. +func NewGUID(guid string) *GUID { + d := []byte(guid) + var d1, d2, d3, d4a, d4b []byte + + switch len(d) { + case 38: + if d[0] != '{' || d[37] != '}' { + return nil + } + d = d[1:37] + fallthrough + case 36: + if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' { + return nil + } + d1 = d[0:8] + d2 = d[9:13] + d3 = d[14:18] + d4a = d[19:23] + d4b = d[24:36] + case 32: + d1 = d[0:8] + d2 = d[8:12] + d3 = d[12:16] + d4a = d[16:20] + d4b = d[20:32] + default: + return nil + } + + var g GUID + var ok1, ok2, ok3, ok4 bool + g.Data1, ok1 = decodeHexUint32(d1) + g.Data2, ok2 = decodeHexUint16(d2) + g.Data3, ok3 = decodeHexUint16(d3) + g.Data4, ok4 = decodeHexByte64(d4a, d4b) + if ok1 && ok2 && ok3 && ok4 { + return &g + } + return nil +} + +func decodeHexUint32(src []byte) (value uint32, ok bool) { + var b1, b2, b3, b4 byte + var ok1, ok2, ok3, ok4 bool + b1, ok1 = decodeHexByte(src[0], src[1]) + b2, ok2 = decodeHexByte(src[2], src[3]) + b3, ok3 = decodeHexByte(src[4], src[5]) + b4, ok4 = decodeHexByte(src[6], src[7]) + value = (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4) + ok = ok1 && ok2 && ok3 && ok4 + return +} + +func decodeHexUint16(src []byte) (value uint16, ok bool) { + var b1, b2 byte + var ok1, ok2 bool + b1, ok1 = decodeHexByte(src[0], src[1]) + b2, ok2 = decodeHexByte(src[2], src[3]) + value = (uint16(b1) << 8) | uint16(b2) + ok = ok1 && ok2 + return +} + +func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) { + var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool + value[0], ok1 = decodeHexByte(s1[0], s1[1]) + value[1], ok2 = decodeHexByte(s1[2], s1[3]) + value[2], ok3 = decodeHexByte(s2[0], s2[1]) + value[3], ok4 = decodeHexByte(s2[2], s2[3]) + value[4], ok5 = decodeHexByte(s2[4], s2[5]) + value[5], ok6 = decodeHexByte(s2[6], s2[7]) + value[6], ok7 = decodeHexByte(s2[8], s2[9]) + value[7], ok8 = decodeHexByte(s2[10], s2[11]) + ok = ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8 + return +} + +func decodeHexByte(c1, c2 byte) (value byte, ok bool) { + var n1, n2 byte + var ok1, ok2 bool + n1, ok1 = decodeHexChar(c1) + n2, ok2 = decodeHexChar(c2) + value = (n1 << 4) | n2 + ok = ok1 && ok2 + return +} + +func decodeHexChar(c byte) (byte, bool) { + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + + return 0, false +} + +// String converts the GUID to string form. It will adhere to this pattern: +// +// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} +// +// If the GUID is nil, the string representation of an empty GUID is returned: +// +// {00000000-0000-0000-0000-000000000000} +func (guid *GUID) String() string { + if guid == nil { + return emptyGUID + } + + var c [38]byte + c[0] = '{' + putUint32Hex(c[1:9], guid.Data1) + c[9] = '-' + putUint16Hex(c[10:14], guid.Data2) + c[14] = '-' + putUint16Hex(c[15:19], guid.Data3) + c[19] = '-' + putByteHex(c[20:24], guid.Data4[0:2]) + c[24] = '-' + putByteHex(c[25:37], guid.Data4[2:8]) + c[37] = '}' + return string(c[:]) +} + +func putUint32Hex(b []byte, v uint32) { + b[0] = hextable[byte(v>>24)>>4] + b[1] = hextable[byte(v>>24)&0x0f] + b[2] = hextable[byte(v>>16)>>4] + b[3] = hextable[byte(v>>16)&0x0f] + b[4] = hextable[byte(v>>8)>>4] + b[5] = hextable[byte(v>>8)&0x0f] + b[6] = hextable[byte(v)>>4] + b[7] = hextable[byte(v)&0x0f] +} + +func putUint16Hex(b []byte, v uint16) { + b[0] = hextable[byte(v>>8)>>4] + b[1] = hextable[byte(v>>8)&0x0f] + b[2] = hextable[byte(v)>>4] + b[3] = hextable[byte(v)&0x0f] +} + +func putByteHex(dst, src []byte) { + for i := 0; i < len(src); i++ { + dst[i*2] = hextable[src[i]>>4] + dst[i*2+1] = hextable[src[i]&0x0f] + } +} + +// IsEqualGUID compares two GUID. +// +// Not constant time comparison. +func IsEqualGUID(guid1 *GUID, guid2 *GUID) bool { + return guid1.Data1 == guid2.Data1 && + guid1.Data2 == guid2.Data2 && + guid1.Data3 == guid2.Data3 && + guid1.Data4[0] == guid2.Data4[0] && + guid1.Data4[1] == guid2.Data4[1] && + guid1.Data4[2] == guid2.Data4[2] && + guid1.Data4[3] == guid2.Data4[3] && + guid1.Data4[4] == guid2.Data4[4] && + guid1.Data4[5] == guid2.Data4[5] && + guid1.Data4[6] == guid2.Data4[6] && + guid1.Data4[7] == guid2.Data4[7] +} diff --git a/v3/pkg/w32/icon.go b/v3/pkg/w32/icon.go new file mode 100644 index 000000000..97d4ad854 --- /dev/null +++ b/v3/pkg/w32/icon.go @@ -0,0 +1,255 @@ +//go:build windows + +package w32 + +import ( + "bytes" + "fmt" + "image" + "image/color" + "image/draw" + "image/png" + "os" + "syscall" + "unsafe" +) + +func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) { + icon := 0 + if isIcon { + icon = 1 + } + r, _, err := procCreateIconFromResourceEx.Call( + presbits, + uintptr(dwResSize), + uintptr(icon), + uintptr(version), + uintptr(cxDesired), + uintptr(cyDesired), + uintptr(flags), + ) + + if r == 0 { + return 0, err + } + return r, nil +} + +func isPNG(fileData []byte) bool { + if len(fileData) < 4 { + return false + } + return string(fileData[:4]) == "\x89PNG" +} + +func isICO(fileData []byte) bool { + if len(fileData) < 4 { + return false + } + return string(fileData[:4]) == "\x00\x00\x01\x00" +} + +// CreateSmallHIconFromImage creates a HICON from a PNG or ICO file +func CreateSmallHIconFromImage(fileData []byte) (HICON, error) { + if len(fileData) < 8 { + return 0, fmt.Errorf("invalid file format") + } + + if !isPNG(fileData) && !isICO(fileData) { + return 0, fmt.Errorf("unsupported file format") + } + iconWidth := GetSystemMetrics(SM_CXSMICON) + iconHeight := GetSystemMetrics(SM_CYSMICON) + icon, err := CreateIconFromResourceEx( + uintptr(unsafe.Pointer(&fileData[0])), + uint32(len(fileData)), + true, + 0x00030000, + iconWidth, + iconHeight, + LR_DEFAULTSIZE) + return HICON(icon), err +} + +// CreateLargeHIconFromImage creates a HICON from a PNG or ICO file +func CreateLargeHIconFromImage(fileData []byte) (HICON, error) { + if len(fileData) < 8 { + return 0, fmt.Errorf("invalid file format") + } + + if !isPNG(fileData) && !isICO(fileData) { + return 0, fmt.Errorf("unsupported file format") + } + iconWidth := GetSystemMetrics(SM_CXICON) + iconHeight := GetSystemMetrics(SM_CXICON) + icon, err := CreateIconFromResourceEx( + uintptr(unsafe.Pointer(&fileData[0])), + uint32(len(fileData)), + true, + 0x00030000, + iconWidth, + iconHeight, + LR_DEFAULTSIZE) + return HICON(icon), err +} + +type ICONINFO struct { + FIcon int32 + XHotspot int32 + YHotspot int32 + HbmMask syscall.Handle + HbmColor syscall.Handle +} + +func SaveHIconAsPNG(hIcon HICON, filePath string) error { + // Load necessary DLLs + user32 := syscall.NewLazyDLL("user32.dll") + gdi32 := syscall.NewLazyDLL("gdi32.dll") + + // Get procedures + getIconInfo := user32.NewProc("GetIconInfo") + getObject := gdi32.NewProc("GetObjectW") + createCompatibleDC := gdi32.NewProc("CreateCompatibleDC") + selectObject := gdi32.NewProc("SelectObject") + getDIBits := gdi32.NewProc("GetDIBits") + deleteObject := gdi32.NewProc("DeleteObject") + deleteDC := gdi32.NewProc("DeleteDC") + + // Get icon info + var iconInfo ICONINFO + ret, _, err := getIconInfo.Call( + uintptr(hIcon), + uintptr(unsafe.Pointer(&iconInfo)), + ) + if ret == 0 { + return err + } + defer deleteObject.Call(uintptr(iconInfo.HbmMask)) + defer deleteObject.Call(uintptr(iconInfo.HbmColor)) + + // Get bitmap info + var bmp BITMAP + ret, _, err = getObject.Call( + uintptr(iconInfo.HbmColor), + unsafe.Sizeof(bmp), + uintptr(unsafe.Pointer(&bmp)), + ) + if ret == 0 { + return err + } + + // Create DC + hdc, _, _ := createCompatibleDC.Call(0) + if hdc == 0 { + return syscall.EINVAL + } + defer deleteDC.Call(hdc) + + // Select bitmap into DC + oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor)) + defer selectObject.Call(hdc, oldBitmap) + + // Prepare bitmap info header + var bi BITMAPINFO + bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader)) + bi.BmiHeader.BiWidth = bmp.BmWidth + bi.BmiHeader.BiHeight = bmp.BmHeight + bi.BmiHeader.BiPlanes = 1 + bi.BmiHeader.BiBitCount = 32 + bi.BmiHeader.BiCompression = BI_RGB + + // Allocate memory for bitmap bits + width, height := int(bmp.BmWidth), int(bmp.BmHeight) + bufferSize := width * height * 4 + bits := make([]byte, bufferSize) + + // Get bitmap bits + ret, _, err = getDIBits.Call( + hdc, + uintptr(iconInfo.HbmColor), + 0, + uintptr(bmp.BmHeight), + uintptr(unsafe.Pointer(&bits[0])), + uintptr(unsafe.Pointer(&bi)), + DIB_RGB_COLORS, + ) + if ret == 0 { + return err + } + + // Create Go image + img := image.NewRGBA(image.Rect(0, 0, width, height)) + + // Convert DIB to RGBA + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + // DIB is bottom-up, so we need to invert Y + dibIndex := ((height-1-y)*width + x) * 4 + + // BGRA to RGBA + b := bits[dibIndex] + g := bits[dibIndex+1] + r := bits[dibIndex+2] + a := bits[dibIndex+3] + + // Set pixel in the image + img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a}) + } + } + + // Create output file + outFile, err := os.Create(filePath) + if err != nil { + return err + } + defer outFile.Close() + + // Encode and save the image + return png.Encode(outFile, img) +} + +func SetWindowIcon(hwnd HWND, icon HICON) { + SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon)) +} + +func pngToImage(data []byte) (*image.RGBA, error) { + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, bounds.Min, draw.Src) + return rgba, nil +} + +func SetMenuIcons(parentMenu HMENU, itemID int, unchecked []byte, checked []byte) error { + if unchecked == nil { + return fmt.Errorf("invalid unchecked bitmap") + } + var err error + var uncheckedIcon, checkedIcon HBITMAP + var uncheckedImage, checkedImage *image.RGBA + uncheckedImage, err = pngToImage(unchecked) + if err != nil { + return err + } + uncheckedIcon, err = CreateHBITMAPFromImage(uncheckedImage) + if err != nil { + return err + } + if checked != nil { + checkedImage, err = pngToImage(checked) + if err != nil { + return err + } + checkedIcon, err = CreateHBITMAPFromImage(checkedImage) + if err != nil { + return err + } + } else { + checkedIcon = uncheckedIcon + } + return SetMenuItemBitmaps(parentMenu, uint32(itemID), MF_BYCOMMAND, checkedIcon, uncheckedIcon) +} diff --git a/v3/pkg/w32/idataobject.go b/v3/pkg/w32/idataobject.go new file mode 100644 index 000000000..cf56d0934 --- /dev/null +++ b/v3/pkg/w32/idataobject.go @@ -0,0 +1,168 @@ +//go:build windows + +package w32 + +import ( + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +type IDataObjectVtbl struct { + IUnknownVtbl + GetData ComProc + GetDataHere ComProc + QueryGetData ComProc + GetCanonicalFormatEtc ComProc + SetData ComProc + EnumFormatEtc ComProc + DAdvise ComProc +} + +type IDataObject struct { + Vtbl *IDataObjectVtbl +} + +func (i *IDataObject) AddRef() uintptr { + refCounter, _, _ := i.Vtbl.AddRef.Call(uintptr(unsafe.Pointer(i))) + return refCounter +} + +func (i *IDataObject) GetData(formatEtc *FORMATETC, medium *STGMEDIUM) error { + hr, _, err := i.Vtbl.GetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) GetDataHere(formatEtc *FORMATETC, medium *STGMEDIUM) error { + hr, _, err := i.Vtbl.GetDataHere.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) QueryGetData(formatEtc *FORMATETC) error { + hr, _, err := i.Vtbl.QueryGetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) GetCanonicalFormatEtc(inputFormatEtc *FORMATETC, outputFormatEtc *FORMATETC) error { + hr, _, err := i.Vtbl.GetCanonicalFormatEtc.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(inputFormatEtc)), + uintptr(unsafe.Pointer(outputFormatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) SetData(formatEtc *FORMATETC, medium *STGMEDIUM, release bool) error { + hr, _, err := i.Vtbl.SetData.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(unsafe.Pointer(medium)), + uintptr(BoolToBOOL(release)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) EnumFormatEtc(dwDirection uint32, enumFormatEtc **IEnumFORMATETC) error { + hr, _, err := i.Vtbl.EnumFormatEtc.Call( + uintptr(unsafe.Pointer(i)), + uintptr(dwDirection), + uintptr(unsafe.Pointer(enumFormatEtc)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +func (i *IDataObject) DAdvise(formatEtc *FORMATETC, advf uint32, adviseSink *IAdviseSink, pdwConnection *uint32) error { + hr, _, err := i.Vtbl.DAdvise.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(formatEtc)), + uintptr(advf), + uintptr(unsafe.Pointer(adviseSink)), + uintptr(unsafe.Pointer(pdwConnection)), + ) + if windows.Handle(hr) != windows.S_OK { + return syscall.Errno(hr) + } + return err +} + +type DVTargetDevice struct { + TdSize uint32 + TdDriverNameOffset uint16 + TdDeviceNameOffset uint16 + TdPortNameOffset uint16 + TdExtDevmodeOffset uint16 + TdData [1]byte +} + +type FORMATETC struct { + CfFormat uint16 + Ptd *DVTargetDevice + DwAspect uint32 + Lindex int32 + Tymed Tymed +} + +type Tymed uint32 + +const ( + TYMED_HGLOBAL Tymed = 1 + TYMED_FILE Tymed = 2 + TYMED_ISTREAM Tymed = 4 + TYMED_ISTORAGE Tymed = 8 + TYMED_GDI Tymed = 16 + TYMED_MFPICT Tymed = 32 + TYMED_ENHMF Tymed = 64 + TYMED_NULL Tymed = 0 +) + +type STGMEDIUM struct { + Tymed Tymed + Union uintptr + PUnkForRelease IUnknownImpl +} + +func (s STGMEDIUM) FileName() string { + if s.Tymed != TYMED_FILE { + return "" + } + return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(s.Union))) +} + +func (s STGMEDIUM) Release() { + if s.PUnkForRelease != nil { + s.PUnkForRelease.Release() + } +} + +type IEnumFORMATETC struct{} +type IAdviseSink struct{} +type IEnumStatData struct{} diff --git a/v3/pkg/w32/idispatch.go b/v3/pkg/w32/idispatch.go new file mode 100644 index 000000000..4f610f3ff --- /dev/null +++ b/v3/pkg/w32/idispatch.go @@ -0,0 +1,45 @@ +//go:build windows + +/* + * Copyright (C) 2019 The Winc Authors. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "unsafe" +) + +type pIDispatchVtbl struct { + pQueryInterface uintptr + pAddRef uintptr + pRelease uintptr + pGetTypeInfoCount uintptr + pGetTypeInfo uintptr + pGetIDsOfNames uintptr + pInvoke uintptr +} + +type IDispatch struct { + lpVtbl *pIDispatchVtbl +} + +func (this *IDispatch) QueryInterface(id *GUID) *IDispatch { + return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id) +} + +func (this *IDispatch) AddRef() int32 { + return ComAddRef((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IDispatch) Release() int32 { + return ComRelease((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IDispatch) GetIDsOfName(names []string) []int32 { + return ComGetIDsOfName(this, names) +} + +func (this *IDispatch) Invoke(dispid int32, dispatch int16, params ...interface{}) *VARIANT { + return ComInvoke(this, dispid, dispatch, params...) +} diff --git a/v3/pkg/w32/idroptarget.go b/v3/pkg/w32/idroptarget.go new file mode 100644 index 000000000..4c54ba66c --- /dev/null +++ b/v3/pkg/w32/idroptarget.go @@ -0,0 +1,134 @@ +//go:build windows + +package w32 + +import ( + "github.com/wailsapp/go-webview2/pkg/combridge" + "golang.org/x/sys/windows" +) + +var ( + DROPEFFECT_NONE DWORD = 0 + DROPEFFECT_COPY DWORD = 1 + DROPEFFECT_MOVE DWORD = 2 + DROPEFFECT_LINK DWORD = 4 +) + +const ( + DRAGDROP_E_ALREADYREGISTERED = 0x80040101 + DRAGDROP_E_INVALIDHWND = 0x80040102 +) + +func _NOP(_ uintptr) uintptr { + return uintptr(windows.S_FALSE) +} + +func init() { + combridge.RegisterVTable[combridge.IUnknown, iDropTarget]( + "{00000122-0000-0000-C000-000000000046}", + _iDropTargetDragEnter, + _iDropTargetDragOver, + _iDropTargetDragLeave, + _iDropTargetDrop, + ) +} + +func _iDropTargetDragEnter(this uintptr, dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).DragEnter(dataObject, grfKeyState, point, pdfEffect) +} + +func _iDropTargetDragOver(this uintptr, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).DragOver(grfKeyState, point, pdfEffect) +} + +func _iDropTargetDragLeave(this uintptr) uintptr { + return combridge.Resolve[iDropTarget](this).DragLeave() +} + +func _iDropTargetDrop(this uintptr, dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + return combridge.Resolve[iDropTarget](this).Drop(dataObject, grfKeyState, point, pdfEffect) +} + +type iDropTarget interface { + combridge.IUnknown + + DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr + DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr + DragLeave() uintptr + Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr +} + +var _ iDropTarget = &DropTarget{} + +type DropTarget struct { + combridge.IUnknownImpl + OnEnterEffect DWORD + OnOverEffect DWORD + OnEnter func() + OnLeave func() + OnOver func() + OnDrop func(filenames []string) +} + +func NewDropTarget() *DropTarget { + result := &DropTarget{ + OnEnterEffect: DROPEFFECT_COPY, + OnOverEffect: DROPEFFECT_COPY, + } + return result +} + +func (d *DropTarget) DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + *pdfEffect = d.OnEnterEffect + if d.OnEnter != nil { + d.OnEnter() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + *pdfEffect = d.OnOverEffect + if d.OnOver != nil { + d.OnOver() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) DragLeave() uintptr { + if d.OnLeave != nil { + d.OnLeave() + } + return uintptr(windows.S_OK) +} + +func (d *DropTarget) Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr { + + if d.OnDrop == nil { + return uintptr(windows.S_OK) + } + + // Extract filenames from dataObject + var filenames []string + var formatETC = FORMATETC{ + CfFormat: CF_HDROP, + Tymed: TYMED_HGLOBAL, + } + + var stgMedium STGMEDIUM + + err := dataObject.GetData(&formatETC, &stgMedium) + if err != nil && err != windows.ERROR_SUCCESS { + return uintptr(windows.S_FALSE) + } + defer stgMedium.Release() + hDrop := stgMedium.Union + _, numFiles := DragQueryFile(hDrop, 0xFFFFFFFF) + for i := uint(0); i < numFiles; i++ { + filename, _ := DragQueryFile(hDrop, i) + filenames = append(filenames, filename) + } + + d.OnDrop(filenames) + + return uintptr(windows.S_OK) +} diff --git a/v3/pkg/w32/image.go b/v3/pkg/w32/image.go new file mode 100644 index 000000000..1c7520f36 --- /dev/null +++ b/v3/pkg/w32/image.go @@ -0,0 +1,55 @@ +//go:build windows + +package w32 + +import ( + "image" + "syscall" + "unsafe" +) + +func CreateHBITMAPFromImage(img *image.RGBA) (HBITMAP, error) { + bounds := img.Bounds() + width, height := bounds.Dx(), bounds.Dy() + + // Create a BITMAPINFO structure for the DIB + bmi := BITMAPINFO{ + BmiHeader: BITMAPINFOHEADER{ + BiSize: uint32(unsafe.Sizeof(BITMAPINFOHEADER{})), + BiWidth: int32(width), + BiHeight: int32(-height), // negative to indicate top-down bitmap + BiPlanes: 1, + BiBitCount: 32, + BiCompression: BI_RGB, + BiSizeImage: uint32(width * height * 4), // RGBA = 4 bytes + }, + } + + // Create the DIB section + var bits unsafe.Pointer + + hbmp := CreateDIBSection(0, &bmi, DIB_RGB_COLORS, &bits, 0, 0) + if hbmp == 0 { + return 0, syscall.GetLastError() + } + + // Copy the pixel data from the Go image to the DIB section + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + i := img.PixOffset(x, y) + r := img.Pix[i+0] + g := img.Pix[i+1] + b := img.Pix[i+2] + a := img.Pix[i+3] + + // Write the RGBA pixel data to the DIB section (BGR order) + offset := y*width*4 + x*4 + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 0))) = b + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 1))) = g + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 2))) = r + *((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 3))) = a + } + } + + return hbmp, nil +} diff --git a/v3/pkg/w32/istream.go b/v3/pkg/w32/istream.go new file mode 100644 index 000000000..a47fbbce1 --- /dev/null +++ b/v3/pkg/w32/istream.go @@ -0,0 +1,33 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "unsafe" +) + +type pIStreamVtbl struct { + pQueryInterface uintptr + pAddRef uintptr + pRelease uintptr +} + +type IStream struct { + lpVtbl *pIStreamVtbl +} + +func (this *IStream) QueryInterface(id *GUID) *IDispatch { + return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id) +} + +func (this *IStream) AddRef() int32 { + return ComAddRef((*IUnknown)(unsafe.Pointer(this))) +} + +func (this *IStream) Release() int32 { + return ComRelease((*IUnknown)(unsafe.Pointer(this))) +} diff --git a/v3/pkg/w32/kernel32.go b/v3/pkg/w32/kernel32.go new file mode 100644 index 000000000..affd497cd --- /dev/null +++ b/v3/pkg/w32/kernel32.go @@ -0,0 +1,337 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + + procGetModuleHandle = modkernel32.NewProc("GetModuleHandleW") + procMulDiv = modkernel32.NewProc("MulDiv") + procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow") + procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") + procGetCurrentThreadId = modkernel32.NewProc("GetCurrentThreadId") + procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives") + procGetLogicalDriveStrings = modkernel32.NewProc("GetLogicalDriveStringsW") + procGetUserDefaultLCID = modkernel32.NewProc("GetUserDefaultLCID") + procLstrlen = modkernel32.NewProc("lstrlenW") + procLstrcpy = modkernel32.NewProc("lstrcpyW") + procGlobalAlloc = modkernel32.NewProc("GlobalAlloc") + procGlobalFree = modkernel32.NewProc("GlobalFree") + procGlobalLock = modkernel32.NewProc("GlobalLock") + procGlobalUnlock = modkernel32.NewProc("GlobalUnlock") + procMoveMemory = modkernel32.NewProc("RtlMoveMemory") + procFindResource = modkernel32.NewProc("FindResourceW") + procSizeofResource = modkernel32.NewProc("SizeofResource") + procLockResource = modkernel32.NewProc("LockResource") + procLoadResource = modkernel32.NewProc("LoadResource") + procGetLastError = modkernel32.NewProc("GetLastError") + procOpenProcess = modkernel32.NewProc("OpenProcess") + procTerminateProcess = modkernel32.NewProc("TerminateProcess") + procCloseHandle = modkernel32.NewProc("CloseHandle") + procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") + procModule32First = modkernel32.NewProc("Module32FirstW") + procModule32Next = modkernel32.NewProc("Module32NextW") + procGetSystemTimes = modkernel32.NewProc("GetSystemTimes") + procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = modkernel32.NewProc("SetConsoleTextAttribute") + procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW") + procGetProcessTimes = modkernel32.NewProc("GetProcessTimes") + procSetSystemTime = modkernel32.NewProc("SetSystemTime") + procGetSystemTime = modkernel32.NewProc("GetSystemTime") +) + +func GetModuleHandle(modulename string) HINSTANCE { + var mn uintptr + if modulename == "" { + mn = 0 + } else { + mn = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(modulename))) + } + ret, _, _ := procGetModuleHandle.Call(mn) + return HINSTANCE(ret) +} + +func GetApplicationHandle() HINSTANCE { + ret, _, _ := procGetModuleHandle.Call(0) + return ret +} + +func MulDiv(number, numerator, denominator int) int { + ret, _, _ := procMulDiv.Call( + uintptr(number), + uintptr(numerator), + uintptr(denominator)) + + return int(ret) +} + +func GetConsoleWindow() HWND { + ret, _, _ := procGetConsoleWindow.Call() + + return HWND(ret) +} + +func GetCurrentThread() HANDLE { + ret, _, _ := procGetCurrentThread.Call() + + return HANDLE(ret) +} + +func GetCurrentThreadId() HANDLE { + ret, _, _ := procGetCurrentThreadId.Call() + + return HANDLE(ret) +} + +func GetLogicalDrives() uint32 { + ret, _, _ := procGetLogicalDrives.Call() + + return uint32(ret) +} + +func GetUserDefaultLCID() uint32 { + ret, _, _ := procGetUserDefaultLCID.Call() + + return uint32(ret) +} + +func Lstrlen(lpString *uint16) int { + ret, _, _ := procLstrlen.Call(uintptr(unsafe.Pointer(lpString))) + + return int(ret) +} + +func Lstrcpy(buf []uint16, lpString *uint16) { + procLstrcpy.Call( + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(lpString))) +} + +func GlobalAlloc(uFlags uint, dwBytes uint32) HGLOBAL { + ret, _, _ := procGlobalAlloc.Call( + uintptr(uFlags), + uintptr(dwBytes)) + + if ret == 0 { + panic("GlobalAlloc failed") + } + + return HGLOBAL(ret) +} + +func GlobalFree(hMem HGLOBAL) { + ret, _, _ := procGlobalFree.Call(uintptr(hMem)) + + if ret != 0 { + panic("GlobalFree failed") + } +} + +func GlobalLock(hMem HGLOBAL) unsafe.Pointer { + ret, _, _ := procGlobalLock.Call(uintptr(hMem)) + + if ret == 0 { + panic("GlobalLock failed") + } + + return unsafe.Pointer(ret) +} + +func GlobalUnlock(hMem HGLOBAL) bool { + ret, _, _ := procGlobalUnlock.Call(uintptr(hMem)) + + return ret != 0 +} + +func MoveMemory(destination, source unsafe.Pointer, length uint32) { + procMoveMemory.Call( + uintptr(unsafe.Pointer(destination)), + uintptr(source), + uintptr(length)) +} + +func FindResource(hModule HMODULE, lpName, lpType *uint16) (HRSRC, error) { + ret, _, _ := procFindResource.Call( + uintptr(hModule), + uintptr(unsafe.Pointer(lpName)), + uintptr(unsafe.Pointer(lpType))) + + if ret == 0 { + return 0, syscall.GetLastError() + } + + return HRSRC(ret), nil +} + +func SizeofResource(hModule HMODULE, hResInfo HRSRC) uint32 { + ret, _, _ := procSizeofResource.Call( + uintptr(hModule), + uintptr(hResInfo)) + + if ret == 0 { + panic("SizeofResource failed") + } + + return uint32(ret) +} + +func LockResource(hResData HGLOBAL) unsafe.Pointer { + ret, _, _ := procLockResource.Call(uintptr(hResData)) + + if ret == 0 { + panic("LockResource failed") + } + + return unsafe.Pointer(ret) +} + +func LoadResource(hModule HMODULE, hResInfo HRSRC) HGLOBAL { + ret, _, _ := procLoadResource.Call( + uintptr(hModule), + uintptr(hResInfo)) + + if ret == 0 { + panic("LoadResource failed") + } + + return HGLOBAL(ret) +} + +func GetLastError() uint32 { + ret, _, _ := procGetLastError.Call() + return uint32(ret) +} + +func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) HANDLE { + inherit := 0 + if inheritHandle { + inherit = 1 + } + + ret, _, _ := procOpenProcess.Call( + uintptr(desiredAccess), + uintptr(inherit), + uintptr(processId)) + return HANDLE(ret) +} + +func TerminateProcess(hProcess HANDLE, uExitCode uint) bool { + ret, _, _ := procTerminateProcess.Call( + uintptr(hProcess), + uintptr(uExitCode)) + return ret != 0 +} + +func CloseHandle(object HANDLE) bool { + ret, _, _ := procCloseHandle.Call( + uintptr(object)) + return ret != 0 +} + +func CreateToolhelp32Snapshot(flags, processId uint32) HANDLE { + ret, _, _ := procCreateToolhelp32Snapshot.Call( + uintptr(flags), + uintptr(processId)) + + if ret <= 0 { + return HANDLE(0) + } + + return HANDLE(ret) +} + +func Module32First(snapshot HANDLE, me *MODULEENTRY32) bool { + ret, _, _ := procModule32First.Call( + uintptr(snapshot), + uintptr(unsafe.Pointer(me))) + + return ret != 0 +} + +func Module32Next(snapshot HANDLE, me *MODULEENTRY32) bool { + ret, _, _ := procModule32Next.Call( + uintptr(snapshot), + uintptr(unsafe.Pointer(me))) + + return ret != 0 +} + +func GetSystemTimes(lpIdleTime, lpKernelTime, lpUserTime *FILETIME) bool { + ret, _, _ := procGetSystemTimes.Call( + uintptr(unsafe.Pointer(lpIdleTime)), + uintptr(unsafe.Pointer(lpKernelTime)), + uintptr(unsafe.Pointer(lpUserTime))) + + return ret != 0 +} + +func GetProcessTimes(hProcess HANDLE, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime *FILETIME) bool { + ret, _, _ := procGetProcessTimes.Call( + uintptr(hProcess), + uintptr(unsafe.Pointer(lpCreationTime)), + uintptr(unsafe.Pointer(lpExitTime)), + uintptr(unsafe.Pointer(lpKernelTime)), + uintptr(unsafe.Pointer(lpUserTime))) + + return ret != 0 +} + +func GetConsoleScreenBufferInfo(hConsoleOutput HANDLE) *CONSOLE_SCREEN_BUFFER_INFO { + var csbi CONSOLE_SCREEN_BUFFER_INFO + ret, _, _ := procGetConsoleScreenBufferInfo.Call( + uintptr(hConsoleOutput), + uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return nil + } + return &csbi +} + +func SetConsoleTextAttribute(hConsoleOutput HANDLE, wAttributes uint16) bool { + ret, _, _ := procSetConsoleTextAttribute.Call( + uintptr(hConsoleOutput), + uintptr(wAttributes)) + return ret != 0 +} + +func GetDiskFreeSpaceEx(dirName string) (r bool, + freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64) { + ret, _, _ := procGetDiskFreeSpaceEx.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(dirName))), + uintptr(unsafe.Pointer(&freeBytesAvailable)), + uintptr(unsafe.Pointer(&totalNumberOfBytes)), + uintptr(unsafe.Pointer(&totalNumberOfFreeBytes))) + return ret != 0, + freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes +} + +func GetSystemTime() *SYSTEMTIME { + var time SYSTEMTIME + procGetSystemTime.Call( + uintptr(unsafe.Pointer(&time))) + return &time +} + +func SetSystemTime(time *SYSTEMTIME) bool { + ret, _, _ := procSetSystemTime.Call( + uintptr(unsafe.Pointer(time))) + return ret != 0 +} + +func GetLogicalDriveStrings(nBufferLength uint32, lpBuffer *uint16) uint32 { + ret, _, _ := procGetLogicalDriveStrings.Call( + uintptr(nBufferLength), + uintptr(unsafe.Pointer(lpBuffer)), + 0) + + return uint32(ret) +} diff --git a/v3/pkg/w32/menubar.go b/v3/pkg/w32/menubar.go new file mode 100644 index 000000000..6b11c1e02 --- /dev/null +++ b/v3/pkg/w32/menubar.go @@ -0,0 +1,980 @@ +//go:build windows + +package w32 + +import ( + "os" + "unsafe" +) + +const ( + OBJID_MENU = -3 + ODT_MENU = 1 + // Menu info flags + MIIM_BACKGROUND = 0x00000002 + MIIM_APPLYTOSUBMENUS = 0x80000000 +) + +var ( + menuTheme HTHEME + procSetMenuInfo = moduser32.NewProc("SetMenuInfo") +) + +type DTTOPTS struct { + DwSize uint32 + DwFlags uint32 + CrText uint32 + CrBorder uint32 + CrShadow uint32 + ITextShadowType int32 + PtShadowOffset POINT + iBorderSize int32 + iFontPropId int32 + IColorPropId int32 + IStateId int32 + FApplyOverlay int32 + IGlowSize int32 + PfnDrawTextCallback uintptr + LParam uintptr +} + +const ( + MENU_POPUPITEM = 14 + MENU_BARITEM = 8 // Menu bar item part ID for theme drawing + DTT_TEXTCOLOR = 1 +) + +// Menu item states +const ( + ODS_SELECTED = 0x0001 + ODS_GRAYED = 0x0002 + ODS_DISABLED = 0x0004 + ODS_CHECKED = 0x0008 + ODS_FOCUS = 0x0010 + ODS_DEFAULT = 0x0020 + ODS_HOTLIGHT = 0x0040 + ODS_INACTIVE = 0x0080 + ODS_NOACCEL = 0x0100 + ODS_NOFOCUSRECT = 0x0200 +) + +// Menu Button Image states +const ( + MBI_NORMAL = 1 + MBI_HOT = 2 + MBI_PUSHED = 3 + MBI_DISABLED = 4 +) + +var ( + procGetMenuItemInfo = moduser32.NewProc("GetMenuItemInfoW") + procGetMenuItemCount = moduser32.NewProc("GetMenuItemCount") + procGetMenuItemRect = moduser32.NewProc("GetMenuItemRect") +) + +func GetMenuItemInfo(hmenu HMENU, item uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procGetMenuItemInfo.Call( + uintptr(hmenu), + uintptr(item), + uintptr(boolToUint(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + ) + return ret != 0 +} + +func GetMenuItemCount(hmenu HMENU) int { + ret, _, _ := procGetMenuItemCount.Call(uintptr(hmenu)) + return int(ret) +} + +func GetMenuItemRect(hwnd HWND, hmenu HMENU, item uint32, rect *RECT) bool { + ret, _, _ := procGetMenuItemRect.Call( + uintptr(hwnd), + uintptr(hmenu), + uintptr(item), + uintptr(unsafe.Pointer(rect)), + ) + return ret != 0 +} + +// Helper function to convert bool to uint +func boolToUint(b bool) uint { + if b { + return 1 + } + return 0 +} + +type UAHMENU struct { + Hmenu HMENU + Hdc HDC + DwFlags uint32 +} + +type MENUBARINFO struct { + CbSize uint32 + Bar RECT + Menu HMENU + Window HWND + BarFocused int32 + Focused int32 +} + +type DRAWITEMSTRUCT struct { + ControlType uint32 + ControlID uint32 + ItemID uint32 + ItemAction uint32 + ItemState uint32 + HWNDItem HWND + HDC HDC + RcItem RECT + ItemData uintptr +} + +type UAHDRAWMENUITEM struct { + DIS DRAWITEMSTRUCT + UM UAHMENU + UAMI UAHMENUITEM +} + +type UAHMENUITEM struct { + Position int + Umim UAHMENUITEMMETRICS + Umpm UAHMENUPOPUPMETRICS +} +type UAHMENUITEMMETRICS struct { + data [32]byte // Total size of the union in bytes (4 DWORDs * 4 bytes each * 2 arrays) +} + +func (u *UAHMENUITEMMETRICS) RgsizeBar() *[2]struct{ cx, cy uint32 } { + return (*[2]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data)) +} + +func (u *UAHMENUITEMMETRICS) RgsizePopup() *[4]struct{ cx, cy uint32 } { + return (*[4]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data)) +} + +type UAHMEASUREMENUITEM struct { + UM UAHMENU + UAMI UAHMENUITEM + Mis MEASUREITEMSTRUCT +} + +type MEASUREITEMSTRUCT struct { + CtlType uint32 + CtlID uint32 + ItemID uint32 + ItemWidth uint32 + ItemHeight uint32 + ItemData uintptr +} + +type UAHMENUPOPUPMETRICS struct { + Rgcx [4]uint32 // Array of 4 DWORDs + FUpdateMaxWidths uint32 // Bit-field represented as a uint32 +} + +// Helper function to get the value of the fUpdateMaxWidths bit-field +func (u *UAHMENUPOPUPMETRICS) GetFUpdateMaxWidths() uint32 { + return u.FUpdateMaxWidths & 0x3 // Mask to get the first 2 bits +} + +// Helper function to set the value of the fUpdateMaxWidths bit-field +func (u *UAHMENUPOPUPMETRICS) SetFUpdateMaxWidths(value uint32) { + u.FUpdateMaxWidths = (u.FUpdateMaxWidths &^ 0x3) | (value & 0x3) // Clear and set the first 2 bits +} + +type MenuBarTheme struct { + TitleBarBackground *uint32 + TitleBarText *uint32 + MenuBarBackground *uint32 // Separate color for menubar + MenuHoverBackground *uint32 + MenuHoverText *uint32 + MenuSelectedBackground *uint32 + MenuSelectedText *uint32 + + // private brushes + titleBarBackgroundBrush HBRUSH + menuBarBackgroundBrush HBRUSH // Separate brush for menubar + menuHoverBackgroundBrush HBRUSH + menuSelectedBackgroundBrush HBRUSH +} + +func createColourWithDefaultColor(color *uint32, def uint32) *uint32 { + if color == nil { + return &def + } + return color +} + +func (d *MenuBarTheme) Init() { + d.TitleBarBackground = createColourWithDefaultColor(d.TitleBarBackground, RGB(25, 25, 26)) + d.TitleBarText = createColourWithDefaultColor(d.TitleBarText, RGB(222, 222, 222)) + d.MenuBarBackground = createColourWithDefaultColor(d.MenuBarBackground, RGB(33, 33, 33)) + d.MenuSelectedText = createColourWithDefaultColor(d.MenuSelectedText, RGB(222, 222, 222)) + d.MenuSelectedBackground = createColourWithDefaultColor(d.MenuSelectedBackground, RGB(48, 48, 48)) + d.MenuHoverText = createColourWithDefaultColor(d.MenuHoverText, RGB(222, 222, 222)) + d.MenuHoverBackground = createColourWithDefaultColor(d.MenuHoverBackground, RGB(48, 48, 48)) + // Create brushes + d.titleBarBackgroundBrush = CreateSolidBrush(*d.TitleBarBackground) + d.menuBarBackgroundBrush = CreateSolidBrush(*d.MenuBarBackground) + d.menuHoverBackgroundBrush = CreateSolidBrush(*d.MenuHoverBackground) + d.menuSelectedBackgroundBrush = CreateSolidBrush(*d.MenuSelectedBackground) +} + +// SetMenuBackground sets the menu background brush directly +func (d *MenuBarTheme) SetMenuBackground(hmenu HMENU) { + var mi MENUINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + mi.FMask = MIIM_BACKGROUND | MIIM_APPLYTOSUBMENUS + mi.HbrBack = d.menuBarBackgroundBrush // Use separate menubar brush + SetMenuInfo(hmenu, &mi) +} + +// SetMenuInfo wrapper function +func SetMenuInfo(hmenu HMENU, lpcmi *MENUINFO) bool { + ret, _, _ := procSetMenuInfo.Call( + uintptr(hmenu), + uintptr(unsafe.Pointer(lpcmi))) + return ret != 0 +} + +func CreateSolidBrush(color COLORREF) HBRUSH { + ret, _, _ := procCreateSolidBrush.Call( + uintptr(color), + ) + return HBRUSH(ret) +} + +func RGB(r, g, b byte) uint32 { + return uint32(r) | uint32(g)<<8 | uint32(b)<<16 +} + +func RGBptr(r, g, b byte) *uint32 { + result := uint32(r) | uint32(g)<<8 | uint32(b)<<16 + return &result +} + +// Track hover state for menubar items when maximized +var ( + currentHoverItem int = -1 + menuIsOpen bool = false // Track if a dropdown menu is open +) + +func MenuBarWndProc(hwnd HWND, msg uint32, wParam WPARAM, lParam LPARAM, theme *MenuBarTheme) (bool, LRESULT) { + // Only proceed if we have a theme (either for dark or light mode) + if theme == nil { + return false, 0 + } + switch msg { + case WM_UAHDRAWMENU: + udm := (*UAHMENU)(unsafe.Pointer(lParam)) + + // Check if maximized first + isMaximized := IsZoomed(hwnd) + + // get the menubar rect + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + return false, 0 + } + + winRect := GetWindowRect(hwnd) + + // the rcBar is offset by the window rect + rc := menuBarInfo.Bar + OffsetRect(&rc, int(-winRect.Left), int(-winRect.Top)) + + // DEBUG: Log the coordinates + // println("WM_UAHDRAWMENU: maximized=", isMaximized) + // println(" menubar screen rect: L=", menuBarInfo.Bar.Left, "T=", menuBarInfo.Bar.Top, + // "R=", menuBarInfo.Bar.Right, "B=", menuBarInfo.Bar.Bottom) + // println(" window rect: L=", winRect.Left, "T=", winRect.Top, + // "R=", winRect.Right, "B=", winRect.Bottom) + // println(" converted rect: L=", rc.Left, "T=", rc.Top, + // "R=", rc.Right, "B=", rc.Bottom) + + // When maximized, Windows extends the window beyond the visible area + // We need to adjust the menubar rect to ensure it's fully visible + if isMaximized { + // Get the frame size - this is how much the window extends beyond visible area when maximized + frameY := GetSystemMetrics(SM_CYSIZEFRAME) + paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER) + + // In Windows 10/11, the actual border is frame + padding + borderSize := frameY + paddedBorder + + // println(" Frame metrics: frameY=", frameY, "paddedBorder=", paddedBorder, "borderSize=", borderSize) + + // First, fill the area from the top of the visible area to the menubar + topFillRect := RECT{ + Left: rc.Left, + Top: int32(borderSize), // Start of visible area in window coordinates + Right: rc.Right, + Bottom: rc.Top, // Up to where the menubar starts + } + FillRect(udm.Hdc, &topFillRect, theme.menuBarBackgroundBrush) + } + + // Fill the entire menubar background with dark color + FillRect(udm.Hdc, &rc, theme.menuBarBackgroundBrush) + + // Paint over the menubar border explicitly + // The border is typically 1-2 pixels at the bottom + borderRect := rc + borderRect.Top = borderRect.Bottom - 1 + borderRect.Bottom = borderRect.Bottom + 2 + FillRect(udm.Hdc, &borderRect, theme.menuBarBackgroundBrush) + + // When maximized, we still need to handle the drawing ourselves + // Some projects found that returning false here causes issues + + // When maximized, manually draw all menu items here + if isMaximized { + // Draw each menu item manually + itemCount := GetMenuItemCount(menuBarInfo.Menu) + for i := 0; i < itemCount; i++ { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) { + // Convert to window coordinates + OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top)) + + // Check if this item is hovered + if i == currentHoverItem { + // Fill with hover background + FillRect(udm.Hdc, &itemRect, theme.menuHoverBackgroundBrush) + } + + // Get menu text + menuString := make([]uint16, 256) + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if GetMenuItemInfo(menuBarInfo.Menu, uint32(i), true, &mii) { + // Draw the text + if i == currentHoverItem { + SetTextColor(udm.Hdc, COLORREF(*theme.MenuHoverText)) + } else { + SetTextColor(udm.Hdc, COLORREF(*theme.TitleBarText)) + } + SetBkMode(udm.Hdc, TRANSPARENT) + DrawText(udm.Hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } + } + } + + // Return the original HDC so Windows can draw the menu text + return true, LRESULT(udm.Hdc) + case WM_DRAWITEM: + // Handle owner-drawn menu items + dis := (*DRAWITEMSTRUCT)(unsafe.Pointer(lParam)) + + // Check if this is a menu item + if dis.ControlType == ODT_MENU { + // Draw the menu item background + var bgBrush HBRUSH + var textColor uint32 + + if dis.ItemState&ODS_SELECTED != 0 { + // Selected state + bgBrush = theme.menuSelectedBackgroundBrush + textColor = *theme.MenuSelectedText + } else { + // Normal state + bgBrush = theme.titleBarBackgroundBrush + textColor = *theme.TitleBarText + } + + // Fill background + FillRect(dis.HDC, &dis.RcItem, bgBrush) + + // Draw text if we have item data + if dis.ItemData != 0 { + text := (*uint16)(unsafe.Pointer(dis.ItemData)) + if text != nil { + // Set text color and draw + SetTextColor(dis.HDC, COLORREF(textColor)) + SetBkMode(dis.HDC, TRANSPARENT) + DrawText(dis.HDC, (*[256]uint16)(unsafe.Pointer(text))[:], -1, &dis.RcItem, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } + + return true, 1 + } + case WM_UAHDRAWMENUITEM: + udmi := (*UAHDRAWMENUITEM)(unsafe.Pointer(lParam)) + + // Check if we're getting menu item draw messages when maximized or fullscreen + isMaximized := IsZoomed(hwnd) + + // Create buffer for menu text + menuString := make([]uint16, 256) + + // Setup menu item info structure + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING | MIIM_SUBMENU, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if !GetMenuItemInfo(udmi.UM.Hmenu, uint32(udmi.UAMI.Position), true, &mii) { + // Failed to get menu item info, let default handler process + return false, 0 + } + + // Remove automatic popup on hover - menus should only open on click + // This was causing the menu to appear at wrong coordinates + dwFlags := uint32(DT_CENTER | DT_SINGLELINE | DT_VCENTER) + + // When maximized/fullscreen, try without VCENTER to see if text appears + if isMaximized && os.Getenv("WAILS_TEST_NO_VCENTER") == "1" { + dwFlags = uint32(DT_CENTER | DT_SINGLELINE) + println(" Using dwFlags without VCENTER") + } + + // Check if this is a menubar item + // When dwFlags has 0x0A00 (2560) it's a menubar item + isMenuBarItem := (udmi.UM.DwFlags&0x0A00) == 0x0A00 || udmi.UM.DwFlags == 0 + + // Use different colors for menubar vs popup items + var bgBrush HBRUSH + var textColor uint32 + + if udmi.DIS.ItemState&ODS_HOTLIGHT != 0 { + // Hot state - use a specific color for hover + bgBrush = theme.menuHoverBackgroundBrush + textColor = *theme.MenuHoverText + } else if udmi.DIS.ItemState&ODS_SELECTED != 0 { + // Selected state + bgBrush = theme.menuSelectedBackgroundBrush + textColor = *theme.MenuSelectedText + } else { + // Normal state + if isMenuBarItem { + // Menubar items in normal state + bgBrush = theme.menuBarBackgroundBrush + textColor = *theme.TitleBarText + } else { + // Popup menu items in normal state - use same color as menubar + bgBrush = theme.menuBarBackgroundBrush + textColor = *theme.TitleBarText + } + } + + // Fill background + if bgBrush != 0 { + FillRect(udmi.UM.Hdc, &udmi.DIS.RcItem, bgBrush) + } + + // Draw text + SetTextColor(udmi.UM.Hdc, COLORREF(textColor)) + SetBkMode(udmi.UM.Hdc, TRANSPARENT) + + // When maximized/fullscreen and menubar item, use the same font settings as drawMenuBarText + if isMaximized && isMenuBarItem { + // Create a non-bold font explicitly + menuFont := LOGFONT{ + Height: -12, // Standard Windows menu font height (9pt) + Weight: 400, // FW_NORMAL (not bold) + CharSet: 1, // DEFAULT_CHARSET + Quality: 5, // CLEARTYPE_QUALITY + PitchAndFamily: 0, // DEFAULT_PITCH + } + // Set font face name to "Segoe UI" (Windows default) + fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0} + copy(menuFont.FaceName[:], fontName) + + hFont := CreateFontIndirect(&menuFont) + if hFont != 0 { + oldFont := SelectObject(udmi.UM.Hdc, HGDIOBJ(hFont)) + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + SelectObject(udmi.UM.Hdc, oldFont) + DeleteObject(HGDIOBJ(hFont)) + } else { + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + } + return true, 4 // CDRF_SKIPDEFAULT + } else { + DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags) + } + + // Return appropriate value based on whether we're in maximized/fullscreen + // For maximized, we need to ensure Windows doesn't override our drawing + if isMaximized { + // Skip default processing to prevent Windows from overriding our colors + return true, 4 // CDRF_SKIPDEFAULT + } + // Return 1 to indicate we've handled the drawing + return true, 1 + case WM_UAHMEASUREMENUITEM: + // Let the default window procedure handle the menu item measurement + // We're not modifying the default sizing anymore + result := DefWindowProc(hwnd, msg, wParam, lParam) + + return true, result + case WM_NCPAINT: + // Paint our custom menubar first + paintDarkMenuBar(hwnd, theme) + + // Then let Windows do its default painting + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Paint again to ensure our painting is on top + paintDarkMenuBar(hwnd, theme) + + return true, result + case WM_NCACTIVATE: + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Force paint the menubar with dark background + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_PAINT: + // Let Windows paint first + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Then paint our menubar + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_ACTIVATEAPP, WM_ACTIVATE: + // Handle app activation/deactivation + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_SIZE, WM_WINDOWPOSCHANGED: + // Handle window size changes + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar after size change + paintDarkMenuBar(hwnd, theme) + + // CRITICAL: Force complete menubar redraw when maximized + if msg == WM_SIZE && wParam == SIZE_MAXIMIZED { + // Invalidate the entire menubar area to force redraw + var mbi MENUBARINFO + mbi.CbSize = uint32(unsafe.Sizeof(mbi)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi) { + InvalidateRect(hwnd, &mbi.Bar, true) + DrawMenuBar(hwnd) + } + } + + return false, result + case WM_SETFOCUS, WM_KILLFOCUS: + // Handle focus changes (e.g., when inspector opens) + result := DefWindowProc(hwnd, msg, wParam, lParam) + + // Repaint menubar after focus change + paintDarkMenuBar(hwnd, theme) + + return false, result + case WM_ERASEBKGND: + // When maximized, draw menubar text here + if IsZoomed(hwnd) { + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + hdc := HDC(wParam) + drawMenuBarText(hwnd, hdc, &menuBarInfo, theme) + } + } + return false, 0 + case WM_NCMOUSEMOVE, WM_MOUSEMOVE: + // Track mouse movement for hover effects when maximized + if IsZoomed(hwnd) { + // Don't process hover changes while menu is open + if menuIsOpen { + return false, 0 + } + + var screenX, screenY int32 + if msg == WM_NCMOUSEMOVE { + // For NC messages, lParam contains screen coordinates + screenX = int32(LOWORD(uint32(lParam))) + screenY = int32(HIWORD(uint32(lParam))) + } else { + // For regular MOUSEMOVE, convert client to screen coordinates + clientX := int32(LOWORD(uint32(lParam))) + clientY := int32(HIWORD(uint32(lParam))) + sx, sy := ClientToScreen(hwnd, int(clientX), int(clientY)) + screenX = int32(sx) + screenY = int32(sy) + } + + // Check if we're over the menubar + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + // menuBarInfo.Bar already contains screen coordinates + // Check if mouse is over menubar using screen coordinates + if screenX >= menuBarInfo.Bar.Left && screenX <= menuBarInfo.Bar.Right && + screenY >= menuBarInfo.Bar.Top && screenY <= menuBarInfo.Bar.Bottom { + + // Always re-request mouse tracking to ensure we get leave messages + TrackMouseEvent(&TRACKMOUSEEVENT{ + CbSize: uint32(unsafe.Sizeof(TRACKMOUSEEVENT{})), + DwFlags: TME_LEAVE | TME_NONCLIENT, + HwndTrack: hwnd, + DwHoverTime: 0, + }) + // Find which menu item we're over + itemCount := GetMenuItemCount(menuBarInfo.Menu) + newHoverItem := -1 + + for i := 0; i < itemCount; i++ { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) { + // itemRect is already in screen coordinates from GetMenuItemRect + // Check using screen coordinates + if screenX >= itemRect.Left && screenX <= itemRect.Right && + screenY >= itemRect.Top && screenY <= itemRect.Bottom { + newHoverItem = i + break + } + } + } + + // If hover item changed, update and redraw just the menubar + if newHoverItem != currentHoverItem { + currentHoverItem = newHoverItem + // Get the actual menubar rect for precise invalidation + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + // Convert to window coordinates + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + // Invalidate only the menubar + InvalidateRect(hwnd, &menubarRect, false) + } + } else { + // Mouse left menubar + if currentHoverItem != -1 { + currentHoverItem = -1 + // Get the actual menubar rect + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + // Convert to window coordinates + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + } + } + } + return false, 0 + case WM_NCLBUTTONDOWN: + // When clicking on menubar, clear hover state immediately + if IsZoomed(hwnd) && currentHoverItem != -1 { + // Check if click is on menubar + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + // Get click position (screen coordinates) + clickX := int32(LOWORD(uint32(lParam))) + clickY := int32(HIWORD(uint32(lParam))) + + if clickX >= menuBarInfo.Bar.Left && clickX <= menuBarInfo.Bar.Right && + clickY >= menuBarInfo.Bar.Top && clickY <= menuBarInfo.Bar.Bottom { + // Click is on menubar - clear hover + currentHoverItem = -1 + } + } + } + return false, 0 + case WM_NCMOUSELEAVE, WM_MOUSELEAVE: + // Clear hover state when mouse leaves (but not if menu is open) + if IsZoomed(hwnd) && currentHoverItem != -1 && !menuIsOpen { + currentHoverItem = -1 + // Get menubar info for precise invalidation + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + } + return false, 0 + case WM_ENTERMENULOOP: + // Menu is being opened - clear hover state + menuIsOpen = true + if IsZoomed(hwnd) && currentHoverItem != -1 { + oldHoverItem := currentHoverItem + currentHoverItem = -1 + // Redraw the previously hovered item to remove hover effect + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + var itemRect RECT + if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(oldHoverItem), &itemRect) { + winRect := GetWindowRect(hwnd) + // Convert to window coordinates + itemRect.Left -= winRect.Left + itemRect.Top -= winRect.Top + itemRect.Right -= winRect.Left + itemRect.Bottom -= winRect.Top + // Add some padding + itemRect.Left -= 5 + itemRect.Right += 5 + itemRect.Top -= 5 + itemRect.Bottom += 5 + InvalidateRect(hwnd, &itemRect, false) + } + } + } + return false, 0 + case WM_EXITMENULOOP: + // Menu has been closed + menuIsOpen = false + // Clear any existing hover state first + currentHoverItem = -1 + // Force a complete menubar redraw + if IsZoomed(hwnd) { + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + winRect := GetWindowRect(hwnd) + menubarRect := menuBarInfo.Bar + menubarRect.Left -= winRect.Left + menubarRect.Top -= winRect.Top + menubarRect.Right -= winRect.Left + menubarRect.Bottom -= winRect.Top + InvalidateRect(hwnd, &menubarRect, false) + } + // Force a timer to restart mouse tracking + SetTimer(hwnd, 1001, 50, 0) + } + return false, 0 + case WM_TIMER: + // Handle our mouse tracking restart timer + if wParam == 1001 { + KillTimer(hwnd, 1001) + if IsZoomed(hwnd) { + // Get current mouse position and simulate a mouse move + x, y, _ := GetCursorPos() + // Check if mouse is over the window + winRect := GetWindowRect(hwnd) + if x >= int(winRect.Left) && x <= int(winRect.Right) && + y >= int(winRect.Top) && y <= int(winRect.Bottom) { + // Check if we're over the menubar specifically + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + if int32(x) >= menuBarInfo.Bar.Left && int32(x) <= menuBarInfo.Bar.Right && + int32(y) >= menuBarInfo.Bar.Top && int32(y) <= menuBarInfo.Bar.Bottom { + // Post a non-client mouse move to restart tracking + PostMessage(hwnd, WM_NCMOUSEMOVE, 0, uintptr(y)<<16|uintptr(x)&0xFFFF) + } else { + // Convert to client coordinates for regular mouse move + clientX, clientY, _ := ScreenToClient(hwnd, x, y) + // Post a mouse move message to restart tracking + PostMessage(hwnd, WM_MOUSEMOVE, 0, uintptr(clientY)<<16|uintptr(clientX)&0xFFFF) + } + } + } + } + return true, 0 + } + return false, 0 + } + return false, 0 +} + +// paintDarkMenuBar paints the menubar with dark background +func paintDarkMenuBar(hwnd HWND, theme *MenuBarTheme) { + // Get menubar info + var menuBarInfo MENUBARINFO + menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo)) + if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) { + return + } + + // Get window DC + hdc := GetWindowDC(hwnd) + if hdc == 0 { + return + } + defer ReleaseDC(hwnd, hdc) + + // Check if window is maximized or fullscreen + isMaximized := IsZoomed(hwnd) + isFullscreen := false + + // Check if window is in fullscreen by checking if it covers the monitor + windowRect := GetWindowRect(hwnd) + monitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY) + var monitorInfo MONITORINFO + monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo)) + if GetMonitorInfo(monitor, &monitorInfo) { + // If window matches monitor bounds, it's fullscreen + if windowRect.Left == monitorInfo.RcMonitor.Left && + windowRect.Top == monitorInfo.RcMonitor.Top && + windowRect.Right == monitorInfo.RcMonitor.Right && + windowRect.Bottom == monitorInfo.RcMonitor.Bottom { + isFullscreen = true + } + } + + // When maximized or fullscreen, we need to handle the special case + if isMaximized || isFullscreen { + // Convert menubar rect from screen to window coordinates + menubarRect := menuBarInfo.Bar + menubarRect.Left -= windowRect.Left + menubarRect.Top -= windowRect.Top + menubarRect.Right -= windowRect.Left + menubarRect.Bottom -= windowRect.Top + + if isMaximized && !isFullscreen { + // Get the frame size (only for maximized, not fullscreen) + frameY := GetSystemMetrics(SM_CYSIZEFRAME) + paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER) + borderSize := frameY + paddedBorder + + // Fill from visible area top to menubar + topFillRect := RECT{ + Left: menubarRect.Left, + Top: int32(borderSize), // Start of visible area + Right: menubarRect.Right, + Bottom: menubarRect.Top, + } + FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush) + } else if isFullscreen { + // In fullscreen, fill from the very top + topFillRect := RECT{ + Left: menubarRect.Left, + Top: 0, // Start from top in fullscreen + Right: menubarRect.Right, + Bottom: menubarRect.Top, + } + FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush) + } + + // Fill the menubar itself + FillRect(hdc, &menubarRect, theme.menuBarBackgroundBrush) + } else { + // Paint the menubar background with dark color + FillRect(hdc, &menuBarInfo.Bar, theme.menuBarBackgroundBrush) + } + + // Get window and client rects to find the non-client area + clientRect := GetClientRect(hwnd) + + // Convert client rect top-left to screen coordinates + _, screenY := ClientToScreen(hwnd, int(clientRect.Left), int(clientRect.Top)) + + // Paint the entire area between menubar and client area + // This should cover any borders + borderRect := RECT{ + Left: 0, + Top: menuBarInfo.Bar.Bottom - windowRect.Top, + Right: windowRect.Right - windowRect.Left, + Bottom: int32(screenY) - windowRect.Top, + } + FillRect(hdc, &borderRect, theme.menuBarBackgroundBrush) + + // When maximized or fullscreen, also draw menubar text + if isMaximized || isFullscreen { + drawMenuBarText(hwnd, hdc, &menuBarInfo, theme) + } +} + +func drawMenuBarText(hwnd HWND, hdc HDC, menuBarInfo *MENUBARINFO, theme *MenuBarTheme) { + // Get the menu handle + hmenu := menuBarInfo.Menu + if hmenu == 0 { + return + } + + // Get the number of menu items + itemCount := GetMenuItemCount(hmenu) + if itemCount <= 0 { + return + } + + // Create a non-bold font explicitly + menuFont := LOGFONT{ + Height: -12, // Standard Windows menu font height (9pt) + Weight: 400, // FW_NORMAL (not bold) + CharSet: 1, // DEFAULT_CHARSET + Quality: 5, // CLEARTYPE_QUALITY + PitchAndFamily: 0, // DEFAULT_PITCH + } + // Set font face name to "Segoe UI" (Windows default) + fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0} + copy(menuFont.FaceName[:], fontName) + + hFont := CreateFontIndirect(&menuFont) + if hFont != 0 { + oldFont := SelectObject(hdc, HGDIOBJ(hFont)) + defer func() { + SelectObject(hdc, oldFont) + DeleteObject(HGDIOBJ(hFont)) + }() + } + + // Set text color and background mode + SetTextColor(hdc, COLORREF(*theme.TitleBarText)) + SetBkMode(hdc, TRANSPARENT) + + // Get the window rect for coordinate conversion + winRect := GetWindowRect(hwnd) + + // Iterate through each menu item + for i := 0; i < itemCount; i++ { + // Get the menu item rect + var itemRect RECT + if !GetMenuItemRect(hwnd, hmenu, uint32(i), &itemRect) { + continue + } + + // Convert to window coordinates + OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top)) + + // Check if this item is hovered + if i == currentHoverItem { + // Fill with hover background + FillRect(hdc, &itemRect, theme.menuHoverBackgroundBrush) + } + + // Get the menu item text + menuString := make([]uint16, 256) + mii := MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})), + FMask: MIIM_STRING, + DwTypeData: &menuString[0], + Cch: uint32(len(menuString) - 1), + } + + if GetMenuItemInfo(hmenu, uint32(i), true, &mii) { + // Set text color based on hover state + if i == currentHoverItem { + SetTextColor(hdc, COLORREF(*theme.MenuHoverText)) + } else { + SetTextColor(hdc, COLORREF(*theme.TitleBarText)) + } + // Draw the text + DrawText(hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER) + } + } +} diff --git a/v3/pkg/w32/ole32.go b/v3/pkg/w32/ole32.go new file mode 100644 index 000000000..a7d204912 --- /dev/null +++ b/v3/pkg/w32/ole32.go @@ -0,0 +1,119 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" + + "github.com/wailsapp/go-webview2/pkg/combridge" +) + +var ( + modole32 = syscall.NewLazyDLL("ole32.dll") + + procCoInitializeEx = modole32.NewProc("CoInitializeEx") + procCoInitialize = modole32.NewProc("CoInitialize") + procOleInitialize = modole32.NewProc("OleInitialize") + procCoUninitialize = modole32.NewProc("CoUninitialize") + procCoCreateInstance = modole32.NewProc("CoCreateInstance") + procCreateStreamOnHGlobal = modole32.NewProc("CreateStreamOnHGlobal") + procRegisterDragDrop = modole32.NewProc("RegisterDragDrop") + procRevokeDragDrop = modole32.NewProc("RevokeDragDrop") +) + +func CoInitializeEx(coInit uintptr) HRESULT { + ret, _, _ := procCoInitializeEx.Call( + 0, + coInit) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CoInitializeEx failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CoInitializeEx failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CoInitializeEx failed with E_UNEXPECTED") + } + + return HRESULT(ret) +} + +func CoInitialize() { + procCoInitialize.Call(0) +} + +func CoUninitialize() { + procCoUninitialize.Call() +} + +func CoCreateInstance(clsid *syscall.GUID, dwClsContext uintptr, riid *syscall.GUID, ppv uintptr) HRESULT { + ret, _, _ := procCoCreateInstance.Call( + uintptr(unsafe.Pointer(clsid)), + 0, + uintptr(dwClsContext), + uintptr(unsafe.Pointer(riid)), + uintptr(ppv)) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CoCreateInstance failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CoCreateInstance failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CoCreateInstance failed with E_UNEXPECTED") + } + + return HRESULT(ret) +} + +func CreateStreamOnHGlobal(hGlobal HGLOBAL, fDeleteOnRelease bool) *IStream { + stream := new(IStream) + ret, _, _ := procCreateStreamOnHGlobal.Call( + uintptr(hGlobal), + uintptr(BoolToBOOL(fDeleteOnRelease)), + uintptr(unsafe.Pointer(&stream))) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CreateStreamOnHGlobal failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CreateStreamOnHGlobal failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CreateStreamOnHGlobal failed with E_UNEXPECTED") + } + + return stream +} +func OleInitialise() { + procOleInitialize.Call() +} + +func RegisterDragDrop(hwnd HWND, dropTarget *DropTarget) error { + + dt := combridge.New[iDropTarget](dropTarget) + hr, _, _ := procRegisterDragDrop.Call( + hwnd, + dt.Ref(), + ) + + if hr != S_OK { + return syscall.Errno(hr) + } + return nil +} + +func RevokeDragDrop(hwnd HWND) error { + hr, _, _ := procRevokeDragDrop.Call( + hwnd, + ) + + if hr != S_OK { + return syscall.Errno(hr) + } + return nil +} diff --git a/v3/pkg/w32/oleaut32.go b/v3/pkg/w32/oleaut32.go new file mode 100644 index 000000000..0bb8ef7da --- /dev/null +++ b/v3/pkg/w32/oleaut32.go @@ -0,0 +1,50 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modoleaut32 = syscall.NewLazyDLL("oleaut32") + + procVariantInit = modoleaut32.NewProc("VariantInit") + procSysAllocString = modoleaut32.NewProc("SysAllocString") + procSysFreeString = modoleaut32.NewProc("SysFreeString") + procSysStringLen = modoleaut32.NewProc("SysStringLen") + procCreateDispTypeInfo = modoleaut32.NewProc("CreateDispTypeInfo") + procCreateStdDispatch = modoleaut32.NewProc("CreateStdDispatch") +) + +func VariantInit(v *VARIANT) { + hr, _, _ := procVariantInit.Call(uintptr(unsafe.Pointer(v))) + if hr != 0 { + panic("Invoke VariantInit error.") + } + return +} + +func SysAllocString(v string) (ss *int16) { + pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))) + ss = (*int16)(unsafe.Pointer(pss)) + return +} + +func SysFreeString(v *int16) { + hr, _, _ := procSysFreeString.Call(uintptr(unsafe.Pointer(v))) + if hr != 0 { + panic("Invoke SysFreeString error.") + } + return +} + +func SysStringLen(v *int16) uint { + l, _, _ := procSysStringLen.Call(uintptr(unsafe.Pointer(v))) + return uint(l) +} diff --git a/v3/pkg/w32/popupmenu.go b/v3/pkg/w32/popupmenu.go new file mode 100644 index 000000000..670eabf9f --- /dev/null +++ b/v3/pkg/w32/popupmenu.go @@ -0,0 +1,96 @@ +//go:build windows + +package w32 + +type Menu HMENU +type PopupMenu Menu + +func (m Menu) destroy() bool { + ret, _, _ := procDestroyMenu.Call(uintptr(m)) + return ret != 0 +} + +func (p PopupMenu) destroy() bool { + return Menu(p).destroy() +} + +func (p PopupMenu) Track(hwnd HWND, flags uint32, x, y int32) bool { + return TrackPopupMenuEx( + HMENU(p), + flags, + x, + y, + hwnd, + nil) +} + +func RemoveMenu(m HMENU, pos, flags int) bool { + ret, _, _ := procRemoveMenu.Call( + uintptr(m), + uintptr(pos), + uintptr(flags)) + return ret != 0 +} + +func (p PopupMenu) Append(flags uint32, id uintptr, text string) bool { + return Menu(p).Append(flags, id, text) +} + +func (m Menu) Append(flags uint32, id uintptr, text string) bool { + return AppendMenu(HMENU(m), flags, id, MustStringToUTF16Ptr(text)) +} + +func (p PopupMenu) Check(id uintptr, checked bool) bool { + return Menu(p).Check(id, checked) +} + +func (m Menu) Check(id uintptr, check bool) bool { + var checkState uint = MF_UNCHECKED + if check { + checkState = MF_CHECKED + } + return CheckMenuItem(HMENU(m), id, checkState) != 0 +} + +func CheckRadio(m HMENU, startID int, endID int, selectedID int) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + m, + uintptr(startID), + uintptr(endID), + uintptr(selectedID), + MF_BYCOMMAND) + return ret != 0 +} + +func (m Menu) CheckRadio(startID int, endID int, selectedID int) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + uintptr(m), + uintptr(startID), + uintptr(endID), + uintptr(selectedID), + MF_BYCOMMAND) + return ret != 0 +} + +func CheckMenuItem(menu HMENU, id uintptr, flags uint) uint { + ret, _, _ := procCheckMenuItem.Call( + menu, + id, + uintptr(flags), + ) + return uint(ret) +} + +func (p PopupMenu) CheckRadio(startID, endID, selectedID int) bool { + return Menu(p).CheckRadio(startID, endID, selectedID) +} + +func NewMenu() HMENU { + ret, _, _ := procCreateMenu.Call() + return HMENU(ret) +} + +func NewPopupMenu() HMENU { + ret, _, _ := procCreatePopupMenu.Call() + return ret +} diff --git a/v3/pkg/w32/screen.go b/v3/pkg/w32/screen.go new file mode 100644 index 000000000..113b1d4cc --- /dev/null +++ b/v3/pkg/w32/screen.go @@ -0,0 +1,143 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "syscall" + "unsafe" +) + +type Screen struct { + MONITORINFOEX + HMonitor uintptr + Name string + IsPrimary bool + IsCurrent bool + ScaleFactor float32 + Rotation float32 +} + +type DISPLAY_DEVICE struct { + cb uint32 + DeviceName [32]uint16 + DeviceString [128]uint16 + StateFlags uint32 + DeviceID [128]uint16 + DeviceKey [128]uint16 +} + +func getMonitorName(deviceName string) (string, error) { + var device DISPLAY_DEVICE + device.cb = uint32(unsafe.Sizeof(device)) + i := uint32(0) + for { + res, _, _ := procEnumDisplayDevices.Call(uintptr(unsafe.Pointer(MustStringToUTF16Ptr(deviceName))), uintptr(i), uintptr(unsafe.Pointer(&device)), 0) + if res == 0 { + break + } + if device.StateFlags&0x1 != 0 { + return syscall.UTF16ToString(device.DeviceString[:]), nil + } + i++ + } + + return "", fmt.Errorf("monitor name not found for device: %s", deviceName) +} + +// I'm not convinced this works properly +func GetRotationForMonitor(displayName [32]uint16) (float32, error) { + var devMode DEVMODE + devMode.DmSize = uint16(unsafe.Sizeof(devMode)) + resp, _, _ := procEnumDisplaySettings.Call(uintptr(unsafe.Pointer(&displayName[0])), ENUM_CURRENT_SETTINGS, uintptr(unsafe.Pointer(&devMode))) + if resp == 0 { + return 0, fmt.Errorf("EnumDisplaySettings failed") + } + + if (devMode.DmFields & DM_DISPLAYORIENTATION) == 0 { + return 0, fmt.Errorf("DM_DISPLAYORIENTATION not set") + } + + switch devMode.DmOrientation { + case DMDO_DEFAULT: + return 0, nil + case DMDO_90: + return 90, nil + case DMDO_180: + return 180, nil + case DMDO_270: + return 270, nil + } + + return -1, nil +} + +func GetAllScreens() ([]*Screen, error) { + var result []*Screen + var errMessage string + + // Get cursor position to determine the current monitor + var cursor POINT + ret, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&cursor))) + if ret == 0 { + return nil, fmt.Errorf("GetCursorPos failed") + } + + // Enumerate the monitors + enumFunc := func(hMonitor uintptr, hdc uintptr, lprcMonitor *RECT, lParam uintptr) uintptr { + monitor := MONITORINFOEX{ + MONITORINFO: MONITORINFO{ + CbSize: uint32(unsafe.Sizeof(MONITORINFOEX{})), + }, + SzDevice: [32]uint16{}, + } + ret, _, _ := procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitor))) + if ret == 0 { + errMessage = "GetMonitorInfo failed" + return 0 // Stop enumeration + } + + screen := &Screen{ + MONITORINFOEX: monitor, + HMonitor: hMonitor, + IsPrimary: monitor.DwFlags == MONITORINFOF_PRIMARY, + IsCurrent: rectContainsPoint(monitor.RcMonitor, cursor), + } + + // Get monitor name + name, err := getMonitorName(syscall.UTF16ToString(monitor.SzDevice[:])) + if err == nil { + screen.Name = name + } + + // Get DPI for monitor + var dpiX, dpiY uint + ret = GetDPIForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) + if ret != S_OK { + errMessage = "GetDpiForMonitor failed" + return 0 // Stop enumeration + } + // Convert to scale factor + screen.ScaleFactor = float32(dpiX) / 96.0 + + // Get rotation of monitor + rot, err := GetRotationForMonitor(monitor.SzDevice) + if err == nil { + screen.Rotation = rot + } + + result = append(result, screen) + return 1 // Continue enumeration + } + + ret, _, _ = procEnumDisplayMonitors.Call(0, 0, syscall.NewCallback(enumFunc), 0) + if ret == 0 { + return nil, fmt.Errorf("EnumDisplayMonitors failed: %s", errMessage) + } + + return result, nil +} + +func rectContainsPoint(r RECT, p POINT) bool { + return p.X >= r.Left && p.X < r.Right && p.Y >= r.Top && p.Y < r.Bottom +} diff --git a/v3/pkg/w32/shcore.go b/v3/pkg/w32/shcore.go new file mode 100644 index 000000000..f2d6c5516 --- /dev/null +++ b/v3/pkg/w32/shcore.go @@ -0,0 +1,54 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "syscall" + "unsafe" +) + +var ( + modshcore = syscall.NewLazyDLL("shcore.dll") + + procGetDpiForMonitor = modshcore.NewProc("GetDpiForMonitor") + procSetProcessDpiAwareness = modshcore.NewProc("SetProcessDpiAwareness") +) + +func HasSetProcessDpiAwarenessFunc() bool { + err := procSetProcessDpiAwareness.Find() + return err == nil +} + +func SetProcessDpiAwareness(val uint) error { + status, r, err := procSetProcessDpiAwareness.Call(uintptr(val)) + if status != S_OK { + return fmt.Errorf("procSetProcessDpiAwareness failed %d: %v %v", status, r, err) + } + return nil +} + +func HasGetDPIForMonitorFunc() bool { + err := procGetDpiForMonitor.Find() + return err == nil +} + +func GetDPIForMonitor(hmonitor HMONITOR, dpiType MONITOR_DPI_TYPE, dpiX *UINT, dpiY *UINT) uintptr { + ret, _, _ := procGetDpiForMonitor.Call( + hmonitor, + uintptr(dpiType), + uintptr(unsafe.Pointer(dpiX)), + uintptr(unsafe.Pointer(dpiY))) + + return ret +} + +func GetNotificationFlyoutBounds() (*RECT, error) { + var rect RECT + res, _, err := procSystemParametersInfo.Call(SPI_GETNOTIFYWINDOWRECT, 0, uintptr(unsafe.Pointer(&rect)), 0) + if res == 0 { + _ = err + return nil, err + } + return &rect, nil +} diff --git a/v3/pkg/w32/shell32.go b/v3/pkg/w32/shell32.go new file mode 100644 index 000000000..33cb72bc5 --- /dev/null +++ b/v3/pkg/w32/shell32.go @@ -0,0 +1,415 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ +package w32 + +import ( + "errors" + "fmt" + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +type CSIDL uint32 + +const ( + CSIDL_DESKTOP = 0x00 + CSIDL_INTERNET = 0x01 + CSIDL_PROGRAMS = 0x02 + CSIDL_CONTROLS = 0x03 + CSIDL_PRINTERS = 0x04 + CSIDL_PERSONAL = 0x05 + CSIDL_FAVORITES = 0x06 + CSIDL_STARTUP = 0x07 + CSIDL_RECENT = 0x08 + CSIDL_SENDTO = 0x09 + CSIDL_BITBUCKET = 0x0A + CSIDL_STARTMENU = 0x0B + CSIDL_MYDOCUMENTS = 0x0C + CSIDL_MYMUSIC = 0x0D + CSIDL_MYVIDEO = 0x0E + CSIDL_DESKTOPDIRECTORY = 0x10 + CSIDL_DRIVES = 0x11 + CSIDL_NETWORK = 0x12 + CSIDL_NETHOOD = 0x13 + CSIDL_FONTS = 0x14 + CSIDL_TEMPLATES = 0x15 + CSIDL_COMMON_STARTMENU = 0x16 + CSIDL_COMMON_PROGRAMS = 0x17 + CSIDL_COMMON_STARTUP = 0x18 + CSIDL_COMMON_DESKTOPDIRECTORY = 0x19 + CSIDL_APPDATA = 0x1A + CSIDL_PRINTHOOD = 0x1B + CSIDL_LOCAL_APPDATA = 0x1C + CSIDL_ALTSTARTUP = 0x1D + CSIDL_COMMON_ALTSTARTUP = 0x1E + CSIDL_COMMON_FAVORITES = 0x1F + CSIDL_INTERNET_CACHE = 0x20 + CSIDL_COOKIES = 0x21 + CSIDL_HISTORY = 0x22 + CSIDL_COMMON_APPDATA = 0x23 + CSIDL_WINDOWS = 0x24 + CSIDL_SYSTEM = 0x25 + CSIDL_PROGRAM_FILES = 0x26 + CSIDL_MYPICTURES = 0x27 + CSIDL_PROFILE = 0x28 + CSIDL_SYSTEMX86 = 0x29 + CSIDL_PROGRAM_FILESX86 = 0x2A + CSIDL_PROGRAM_FILES_COMMON = 0x2B + CSIDL_PROGRAM_FILES_COMMONX86 = 0x2C + CSIDL_COMMON_TEMPLATES = 0x2D + CSIDL_COMMON_DOCUMENTS = 0x2E + CSIDL_COMMON_ADMINTOOLS = 0x2F + CSIDL_ADMINTOOLS = 0x30 + CSIDL_CONNECTIONS = 0x31 + CSIDL_COMMON_MUSIC = 0x35 + CSIDL_COMMON_PICTURES = 0x36 + CSIDL_COMMON_VIDEO = 0x37 + CSIDL_RESOURCES = 0x38 + CSIDL_RESOURCES_LOCALIZED = 0x39 + CSIDL_COMMON_OEM_LINKS = 0x3A + CSIDL_CDBURN_AREA = 0x3B + CSIDL_COMPUTERSNEARME = 0x3D + CSIDL_FLAG_CREATE = 0x8000 + CSIDL_FLAG_DONT_VERIFY = 0x4000 + CSIDL_FLAG_NO_ALIAS = 0x1000 + CSIDL_FLAG_PER_USER_INIT = 0x8000 + CSIDL_FLAG_MASK = 0xFF00 + + NOTIFYICON_VERSION = 4 +) + +var ( + FOLDERID_AccountPictures = NewGUID("{008CA0B1-55B4-4C56-B8A8-4DE4B299D3BE}") + FOLDERID_AddNewPrograms = NewGUID("{DE61D971-5EBC-4F02-A3A9-6C82895E5C04}") + FOLDERID_AdminTools = NewGUID("{724EF170-A42D-4FEF-9F26-B60E846FBA4F}") + FOLDERID_ApplicationShortcuts = NewGUID("{A3918781-E5F2-4890-B3D9-A7E54332328C}") + FOLDERID_AppsFolder = NewGUID("{1E87508D-89C2-42F0-8A7E-645A0F50CA58}") + FOLDERID_AppUpdates = NewGUID("{A305CE99-F527-492B-8B1A-7E76FA98D6E4}") + FOLDERID_CDBurning = NewGUID("{9E52AB10-F80D-49DF-ACB8-4330F5687855}") + FOLDERID_ChangeRemovePrograms = NewGUID("{DF7266AC-9274-4867-8D55-3BD661DE872D}") + FOLDERID_CommonAdminTools = NewGUID("{D0384E7D-BAC3-4797-8F14-CBA229B392B5}") + FOLDERID_CommonOEMLinks = NewGUID("{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}") + FOLDERID_CommonPrograms = NewGUID("{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}") + FOLDERID_CommonStartMenu = NewGUID("{A4115719-D62E-491D-AA7C-E74B8BE3B067}") + FOLDERID_CommonStartup = NewGUID("{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}") + FOLDERID_CommonTemplates = NewGUID("{B94237E7-57AC-4347-9151-B08C6C32D1F7}") + FOLDERID_ComputerFolder = NewGUID("{0AC0837C-BBF8-452A-850D-79D08E667CA7}") + FOLDERID_ConflictFolder = NewGUID("{4BFEFB45-347D-4006-A5BE-AC0CB0567192}") + FOLDERID_ConnectionsFolder = NewGUID("{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}") + FOLDERID_Contacts = NewGUID("{56784854-C6CB-462B-8169-88E350ACB882}") + FOLDERID_ControlPanelFolder = NewGUID("{82A74AEB-AEB4-465C-A014-D097EE346D63}") + FOLDERID_Cookies = NewGUID("{2B0F765D-C0E9-4171-908E-08A611B84FF6}") + FOLDERID_Desktop = NewGUID("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}") + FOLDERID_DeviceMetadataStore = NewGUID("{5CE4A5E9-E4EB-479D-B89F-130C02886155}") + FOLDERID_Documents = NewGUID("{FDD39AD0-238F-46AF-ADB4-6C85480369C7}") + FOLDERID_DocumentsLibrary = NewGUID("{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}") + FOLDERID_Downloads = NewGUID("{374DE290-123F-4565-9164-39C4925E467B}") + FOLDERID_Favorites = NewGUID("{1777F761-68AD-4D8A-87BD-30B759FA33DD}") + FOLDERID_Fonts = NewGUID("{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}") + FOLDERID_Games = NewGUID("{CAC52C1A-B53D-4EDC-92D7-6B2E8AC19434}") + FOLDERID_GameTasks = NewGUID("{054FAE61-4DD8-4787-80B6-090220C4B700}") + FOLDERID_History = NewGUID("{D9DC8A3B-B784-432E-A781-5A1130A75963}") + FOLDERID_HomeGroup = NewGUID("{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}") + FOLDERID_HomeGroupCurrentUser = NewGUID("{9B74B6A3-0DFD-4F11-9E78-5F7800F2E772}") + FOLDERID_ImplicitAppShortcuts = NewGUID("{BCB5256F-79F6-4CEE-B725-DC34E402FD46}") + FOLDERID_InternetCache = NewGUID("{352481E8-33BE-4251-BA85-6007CAEDCF9D}") + FOLDERID_InternetFolder = NewGUID("{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}") + FOLDERID_Libraries = NewGUID("{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}") + FOLDERID_Links = NewGUID("{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}") + FOLDERID_LocalAppData = NewGUID("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}") + FOLDERID_LocalAppDataLow = NewGUID("{A520A1A4-1780-4FF6-BD18-167343C5AF16}") + FOLDERID_LocalizedResourcesDir = NewGUID("{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}") + FOLDERID_Music = NewGUID("{4BD8D571-6D19-48D3-BE97-422220080E43}") + FOLDERID_MusicLibrary = NewGUID("{2112AB0A-C86A-4FFE-A368-0DE96E47012E}") + FOLDERID_NetHood = NewGUID("{C5ABBF53-E17F-4121-8900-86626FC2C973}") + FOLDERID_NetworkFolder = NewGUID("{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}") + FOLDERID_OriginalImages = NewGUID("{2C36C0AA-5812-4B87-BFD0-4CD0DFB19B39}") + FOLDERID_PhotoAlbums = NewGUID("{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}") + FOLDERID_Pictures = NewGUID("{33E28130-4E1E-4676-835A-98395C3BC3BB}") + FOLDERID_PicturesLibrary = NewGUID("{A990AE9F-A03B-4E80-94BC-9912D7504104}") + FOLDERID_Playlists = NewGUID("{DE92C1C7-837F-4F69-A3BB-86E631204A23}") + FOLDERID_PrintersFolder = NewGUID("{76FC4E2D-D6AD-4519-A663-37BD56068185}") + FOLDERID_PrintHood = NewGUID("{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}") + FOLDERID_Profile = NewGUID("{5E6C858F-0E22-4760-9AFE-EA3317B67173}") + FOLDERID_ProgramData = NewGUID("{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}") + FOLDERID_ProgramFiles = NewGUID("{905E63B6-C1BF-494E-B29C-65B732D3D21A}") + FOLDERID_ProgramFilesCommon = NewGUID("{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}") + FOLDERID_ProgramFilesCommonX64 = NewGUID("{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}") + FOLDERID_ProgramFilesCommonX86 = NewGUID("{DE974D24-D9C6-4D3E-BF91-F4455120B917}") + FOLDERID_ProgramFilesX64 = NewGUID("{6D809377-6AF0-444B-8957-A3773F02200E}") + FOLDERID_ProgramFilesX86 = NewGUID("{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}") + FOLDERID_Programs = NewGUID("{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}") + FOLDERID_Public = NewGUID("{DFDF76A2-C82A-4D63-906A-5644AC457385}") + FOLDERID_PublicDesktop = NewGUID("{C4AA340D-F20F-4863-AFEF-1F769F2BE730}") + FOLDERID_PublicDocuments = NewGUID("{ED4824AF-DCE4-45A8-81E2-FC7965083634}") + FOLDERID_PublicDownloads = NewGUID("{3D644C9B-1FB8-4F30-9B45-F670235F79C0}") + FOLDERID_PublicGameTasks = NewGUID("{DEBF2536-E1A8-4C59-B6A2-414586476AEA}") + FOLDERID_PublicLibraries = NewGUID("{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}") + FOLDERID_PublicMusic = NewGUID("{3214FAB5-9757-4298-BB61-92A9DEAA44FF}") + FOLDERID_PublicPictures = NewGUID("{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}") + FOLDERID_PublicRingtones = NewGUID("{E555AB60-153B-4D17-9F04-A5FE99FC15EC}") + FOLDERID_PublicUserTiles = NewGUID("{0482af6c-08f1-4c34-8c90-e17ec98b1e17}") + FOLDERID_PublicVideos = NewGUID("{2400183A-6185-49FB-A2D8-4A392A602BA3}") + FOLDERID_QuickLaunch = NewGUID("{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}") + FOLDERID_Recent = NewGUID("{AE50C081-EBD2-438A-8655-8A092E34987A}") + FOLDERID_RecordedTVLibrary = NewGUID("{1A6FDBA2-F42D-4358-A798-B74D745926C5}") + FOLDERID_RecycleBinFolder = NewGUID("{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}") + FOLDERID_ResourceDir = NewGUID("{8AD10C31-2ADB-4296-A8F7-E4701232C972}") + FOLDERID_Ringtones = NewGUID("{C870044B-F49E-4126-A9C3-B52A1FF411E8}") + FOLDERID_RoamingAppData = NewGUID("{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}") + FOLDERID_RoamingTiles = NewGUID("{AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E}") + FOLDERID_SampleMusic = NewGUID("{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}") + FOLDERID_SamplePictures = NewGUID("{C4900540-2379-4C75-844B-64E6FAF8716B}") + FOLDERID_SamplePlaylists = NewGUID("{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}") + FOLDERID_SampleVideos = NewGUID("{859EAD94-2E85-48AD-A71A-0969CB56A6CD}") + FOLDERID_SavedGames = NewGUID("{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}") + FOLDERID_SavedPictures = NewGUID("{3B193882-D3AD-4EAB-965A-69829D1FB59F}") + FOLDERID_SavedPicturesLibrary = NewGUID("{E25B5812-BE88-4BD9-94B0-29233477B6C3}") + FOLDERID_SavedSearches = NewGUID("{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}") + FOLDERID_SEARCH_CSC = NewGUID("{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}") + FOLDERID_SEARCH_MAPI = NewGUID("{98ec0e18-2098-4d44-8644-66979315a281}") + FOLDERID_SearchHome = NewGUID("{190337d1-b8ca-4121-a639-6d472d16972a}") + FOLDERID_SendTo = NewGUID("{8983036C-27C0-404B-8F08-102D10DCFD74}") + FOLDERID_SidebarDefaultParts = NewGUID("{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}") + FOLDERID_SidebarParts = NewGUID("{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}") + FOLDERID_SkyDrive = NewGUID("{A52BBA46-E9E1-435f-B3D9-28DAA648C0F6}") + FOLDERID_SkyDriveCameraRoll = NewGUID("{767E6811-49CB-4273-87C2-20F355E1085B}") + FOLDERID_SkyDriveDocuments = NewGUID("{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}") + FOLDERID_SkyDriveMusic = NewGUID("{C3F2459E-80D6-45DC-BFEF-1F769F2BE730}") + FOLDERID_SkyDrivePictures = NewGUID("{339719B5-8C47-4894-94C2-D8F77ADD44A6}") + FOLDERID_StartMenu = NewGUID("{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}") + FOLDERID_Startup = NewGUID("{B97D20BB-F46A-4C97-BA10-5E3608430854}") + FOLDERID_SyncManagerFolder = NewGUID("{43668BF8-C14E-49B2-97C9-747784D784B7}") + FOLDERID_SyncResultsFolder = NewGUID("{289a9a43-be44-4057-a41b-587a76d7e7f9}") + FOLDERID_SyncSetupFolder = NewGUID("{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}") + FOLDERID_System = NewGUID("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}") + FOLDERID_SystemX86 = NewGUID("{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}") + FOLDERID_Templates = NewGUID("{A63293E8-664E-48DB-A079-DF759E0509F7}") + FOLDERID_UserPinned = NewGUID("{9E3995AB-1F9C-4F13-B827-48B24B6C7174}") + FOLDERID_UserProfiles = NewGUID("{0762D272-C50A-4BB0-A382-697DCD729B80}") + FOLDERID_UserProgramFiles = NewGUID("{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}") + FOLDERID_UserProgramFilesCommon = NewGUID("{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}") + FOLDERID_UsersFiles = NewGUID("{F3CE0F7C-4901-4ACC-8648-D5D44B04EF8F}") + FOLDERID_UsersLibraries = NewGUID("{A302545D-DEFF-464b-ABE8-61C8648D939B}") + FOLDERID_Videos = NewGUID("{18989B1D-99B5-455B-841C-AB7C74E4DDFC}") + FOLDERID_VideosLibrary = NewGUID("{491E922F-5643-4AF4-A7EB-4E7A138D8174}") + FOLDERID_Windows = NewGUID("{F38BF404-1D43-42F2-9305-67DE0B28FC23}") +) + +var ( + modshell32 = syscall.NewLazyDLL("shell32.dll") + + procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolderW") + procSHGetPathFromIDList = modshell32.NewProc("SHGetPathFromIDListW") + procDragAcceptFiles = modshell32.NewProc("DragAcceptFiles") + procDragQueryFile = modshell32.NewProc("DragQueryFileW") + procDragQueryPoint = modshell32.NewProc("DragQueryPoint") + procDragFinish = modshell32.NewProc("DragFinish") + procShellExecute = modshell32.NewProc("ShellExecuteW") + procExtractIcon = modshell32.NewProc("ExtractIconW") + procGetSpecialFolderPath = modshell32.NewProc("SHGetSpecialFolderPathW") + procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW") + procShellNotifyIconGetRect = modshell32.NewProc("Shell_NotifyIconGetRect") + procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath") + procSHAppBarMessage = modshell32.NewProc("SHAppBarMessage") +) + +type APPBARDATA struct { + CbSize uint32 + HWnd HWND + UCallbackMessage uint32 + UEdge uint32 + Rc RECT + LParam uintptr +} + +func ShellNotifyIcon(cmd uintptr, nid *NOTIFYICONDATA) bool { + ret, _, _ := procShellNotifyIcon.Call(cmd, uintptr(unsafe.Pointer(nid))) + return ret == 1 +} + +func SHBrowseForFolder(bi *BROWSEINFO) uintptr { + ret, _, _ := procSHBrowseForFolder.Call(uintptr(unsafe.Pointer(bi))) + + return ret +} + +func SHGetKnownFolderPath(rfid *GUID, dwFlags uint32, hToken HANDLE) (string, error) { + var path *uint16 + ret, _, _ := procSHGetKnownFolderPath.Call(uintptr(unsafe.Pointer(rfid)), uintptr(dwFlags), hToken, uintptr(unsafe.Pointer(path))) + if ret != uintptr(windows.S_OK) { + return "", fmt.Errorf("SHGetKnownFolderPath failed: %v", ret) + } + return windows.UTF16PtrToString(path), nil +} + +func SHGetPathFromIDList(idl uintptr) string { + buf := make([]uint16, 1024) + procSHGetPathFromIDList.Call( + idl, + uintptr(unsafe.Pointer(&buf[0]))) + + return syscall.UTF16ToString(buf) +} + +func DragAcceptFiles(hwnd HWND, accept bool) { + procDragAcceptFiles.Call( + hwnd, + uintptr(BoolToBOOL(accept))) +} + +func DragQueryFile(hDrop HDROP, iFile uint) (fileName string, fileCount uint) { + ret, _, _ := procDragQueryFile.Call( + hDrop, + uintptr(iFile), + 0, + 0) + + fileCount = uint(ret) + + if iFile != 0xFFFFFFFF { + buf := make([]uint16, fileCount+1) + + ret, _, _ := procDragQueryFile.Call( + hDrop, + uintptr(iFile), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(fileCount+1)) + + if ret == 0 { + panic("Invoke DragQueryFile error.") + } + + fileName = syscall.UTF16ToString(buf) + } + + return +} + +func DragQueryPoint(hDrop HDROP) (x, y int, isClientArea bool) { + var pt POINT + ret, _, _ := procDragQueryPoint.Call( + uintptr(hDrop), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y), (ret == 1) +} + +func DragFinish(hDrop HDROP) { + procDragFinish.Call(uintptr(hDrop)) +} + +func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error { + var op, param, directory uintptr + if len(lpOperation) != 0 { + op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation))) + } + if len(lpParameters) != 0 { + param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters))) + } + if len(lpDirectory) != 0 { + directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory))) + } + + ret, _, _ := procShellExecute.Call( + uintptr(hwnd), + op, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))), + param, + directory, + uintptr(nShowCmd)) + + errorMsg := "" + if ret != 0 && ret <= 32 { + switch int(ret) { + case ERROR_FILE_NOT_FOUND: + errorMsg = "The specified file was not found." + case ERROR_PATH_NOT_FOUND: + errorMsg = "The specified path was not found." + case ERROR_BAD_FORMAT: + errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)." + case SE_ERR_ACCESSDENIED: + errorMsg = "The operating system denied access to the specified file." + case SE_ERR_ASSOCINCOMPLETE: + errorMsg = "The file name association is incomplete or invalid." + case SE_ERR_DDEBUSY: + errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed." + case SE_ERR_DDEFAIL: + errorMsg = "The DDE transaction failed." + case SE_ERR_DDETIMEOUT: + errorMsg = "The DDE transaction could not be completed because the request timed out." + case SE_ERR_DLLNOTFOUND: + errorMsg = "The specified DLL was not found." + case SE_ERR_NOASSOC: + errorMsg = "There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable." + case SE_ERR_OOM: + errorMsg = "There was not enough memory to complete the operation." + case SE_ERR_SHARE: + errorMsg = "A sharing violation occurred." + default: + errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", ret) + } + } else { + return nil + } + + return errors.New(errorMsg) +} + +func ExtractIcon(lpszExeFileName string, nIconIndex int) HICON { + ret, _, _ := procExtractIcon.Call( + 0, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszExeFileName))), + uintptr(nIconIndex)) + + return HICON(ret) +} + +func SHGetSpecialFolderPath(hwndOwner HWND, lpszPath *uint16, csidl CSIDL, fCreate bool) bool { + ret, _, _ := procGetSpecialFolderPath.Call( + uintptr(hwndOwner), + uintptr(unsafe.Pointer(lpszPath)), + uintptr(csidl), + uintptr(BoolToBOOL(fCreate)), + 0, + 0) + + return ret != 0 +} + +func GetSystrayBounds(hwnd HWND, uid uint32) (*RECT, error) { + var rect RECT + identifier := NOTIFYICONIDENTIFIER{ + CbSize: uint32(unsafe.Sizeof(NOTIFYICONIDENTIFIER{})), + HWnd: hwnd, + UId: uid, + } + ret, _, _ := procShellNotifyIconGetRect.Call( + uintptr(unsafe.Pointer(&identifier)), + uintptr(unsafe.Pointer(&rect))) + + if ret != S_OK { + return nil, syscall.GetLastError() + } + + return &rect, nil +} + +// GetTaskbarPosition returns the location of the taskbar. +func GetTaskbarPosition() *APPBARDATA { + var result APPBARDATA + result.CbSize = uint32(unsafe.Sizeof(APPBARDATA{})) + ret, _, _ := procSHAppBarMessage.Call( + ABM_GETTASKBARPOS, + uintptr(unsafe.Pointer(&result))) + if ret == 0 { + return nil + } + + return &result +} diff --git a/v3/pkg/w32/shlwapi.go b/v3/pkg/w32/shlwapi.go new file mode 100644 index 000000000..89d17ce6f --- /dev/null +++ b/v3/pkg/w32/shlwapi.go @@ -0,0 +1,26 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + modshlwapi = syscall.NewLazyDLL("shlwapi.dll") + + procSHCreateMemStream = modshlwapi.NewProc("SHCreateMemStream") +) + +func SHCreateMemStream(data []byte) (uintptr, error) { + ret, _, err := procSHCreateMemStream.Call( + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + ) + if ret == 0 { + return 0, err + } + + return ret, nil +} diff --git a/v3/pkg/w32/taskbar.go b/v3/pkg/w32/taskbar.go new file mode 100644 index 000000000..7f2a697d6 --- /dev/null +++ b/v3/pkg/w32/taskbar.go @@ -0,0 +1,95 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + CLSID_TaskbarList = syscall.GUID{Data1: 0x56FDF344, Data2: 0xFD6D, Data3: 0x11D0, Data4: [8]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90}} + IID_ITaskbarList3 = syscall.GUID{Data1: 0xEA1AFB91, Data2: 0x9E28, Data3: 0x4B86, Data4: [8]byte{0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF}} +) + +// ITaskbarList3 interface for Windows taskbar functionality +type ITaskbarList3 struct { + lpVtbl *taskbarList3Vtbl +} + +type taskbarList3Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + HrInit uintptr + AddTab uintptr + DeleteTab uintptr + ActivateTab uintptr + SetActiveAlt uintptr + MarkFullscreenWindow uintptr + SetProgressValue uintptr + SetProgressState uintptr + RegisterTab uintptr + UnregisterTab uintptr + SetTabOrder uintptr + SetTabActive uintptr + ThumbBarAddButtons uintptr + ThumbBarUpdateButtons uintptr + ThumbBarSetImageList uintptr + SetOverlayIcon uintptr + SetThumbnailTooltip uintptr + SetThumbnailClip uintptr +} + +// NewTaskbarList3 creates a new instance of ITaskbarList3 +func NewTaskbarList3() (*ITaskbarList3, error) { + const COINIT_APARTMENTTHREADED = 0x2 + + if hrInit := CoInitializeEx(COINIT_APARTMENTTHREADED); hrInit != 0 && hrInit != 0x1 { + return nil, syscall.Errno(hrInit) + } + + var taskbar *ITaskbarList3 + hr := CoCreateInstance( + &CLSID_TaskbarList, + CLSCTX_INPROC_SERVER, + &IID_ITaskbarList3, + uintptr(unsafe.Pointer(&taskbar)), + ) + + if hr != 0 { + CoUninitialize() + return nil, syscall.Errno(hr) + } + + if r, _, _ := syscall.SyscallN(taskbar.lpVtbl.HrInit, uintptr(unsafe.Pointer(taskbar))); r != 0 { + syscall.SyscallN(taskbar.lpVtbl.Release, uintptr(unsafe.Pointer(taskbar))) + CoUninitialize() + return nil, syscall.Errno(r) + } + + return taskbar, nil +} + +// SetOverlayIcon sets an overlay icon on the taskbar +func (t *ITaskbarList3) SetOverlayIcon(hwnd HWND, hIcon HICON, description *uint16) error { + ret, _, _ := syscall.SyscallN( + t.lpVtbl.SetOverlayIcon, + uintptr(unsafe.Pointer(t)), + uintptr(hwnd), + uintptr(hIcon), + uintptr(unsafe.Pointer(description)), + ) + if ret != 0 { + return syscall.Errno(ret) + } + return nil +} + +// Release releases the ITaskbarList3 interface +func (t *ITaskbarList3) Release() { + if t != nil { + syscall.SyscallN(t.lpVtbl.Release, uintptr(unsafe.Pointer(t))) + CoUninitialize() + } +} diff --git a/v3/pkg/w32/theme.go b/v3/pkg/w32/theme.go new file mode 100644 index 000000000..0e8a8ef17 --- /dev/null +++ b/v3/pkg/w32/theme.go @@ -0,0 +1,345 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +type DWMWINDOWATTRIBUTE int32 + +const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19 +const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20 +const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34 +const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35 +const DwmwaTextColor DWMWINDOWATTRIBUTE = 36 +const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38 + +const SPI_GETHIGHCONTRAST = 0x0042 +const HCF_HIGHCONTRASTON = 0x00000001 + +type WINDOWCOMPOSITIONATTRIB DWORD + +type HTHEME HANDLE + +const ( + WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0 + WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1 + WCA_NCRENDERING_POLICY WINDOWCOMPOSITIONATTRIB = 2 + WCA_TRANSITIONS_FORCEDISABLED WINDOWCOMPOSITIONATTRIB = 3 + WCA_ALLOW_NCPAINT WINDOWCOMPOSITIONATTRIB = 4 + WCA_CAPTION_BUTTON_BOUNDS WINDOWCOMPOSITIONATTRIB = 5 + WCA_NONCLIENT_RTL_LAYOUT WINDOWCOMPOSITIONATTRIB = 6 + WCA_FORCE_ICONIC_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 7 + WCA_EXTENDED_FRAME_BOUNDS WINDOWCOMPOSITIONATTRIB = 8 + WCA_HAS_ICONIC_BITMAP WINDOWCOMPOSITIONATTRIB = 9 + WCA_THEME_ATTRIBUTES WINDOWCOMPOSITIONATTRIB = 10 + WCA_NCRENDERING_EXILED WINDOWCOMPOSITIONATTRIB = 11 + WCA_NCADORNMENTINFO WINDOWCOMPOSITIONATTRIB = 12 + WCA_EXCLUDED_FROM_LIVEPREVIEW WINDOWCOMPOSITIONATTRIB = 13 + WCA_VIDEO_OVERLAY_ACTIVE WINDOWCOMPOSITIONATTRIB = 14 + WCA_FORCE_ACTIVEWINDOW_APPEARANCE WINDOWCOMPOSITIONATTRIB = 15 + WCA_DISALLOW_PEEK WINDOWCOMPOSITIONATTRIB = 16 + WCA_CLOAK WINDOWCOMPOSITIONATTRIB = 17 + WCA_CLOAKED WINDOWCOMPOSITIONATTRIB = 18 + WCA_ACCENT_POLICY WINDOWCOMPOSITIONATTRIB = 19 + WCA_FREEZE_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 20 + WCA_EVER_UNCLOAKED WINDOWCOMPOSITIONATTRIB = 21 + WCA_VISUAL_OWNER WINDOWCOMPOSITIONATTRIB = 22 + WCA_HOLOGRAPHIC WINDOWCOMPOSITIONATTRIB = 23 + WCA_EXCLUDED_FROM_DDA WINDOWCOMPOSITIONATTRIB = 24 + WCA_PASSIVEUPDATEMODE WINDOWCOMPOSITIONATTRIB = 25 + WCA_USEDARKMODECOLORS WINDOWCOMPOSITIONATTRIB = 26 + WCA_CORNER_STYLE WINDOWCOMPOSITIONATTRIB = 27 + WCA_PART_COLOR WINDOWCOMPOSITIONATTRIB = 28 + WCA_DISABLE_MOVESIZE_FEEDBACK WINDOWCOMPOSITIONATTRIB = 29 + WCA_LAST WINDOWCOMPOSITIONATTRIB = 30 +) + +type WINDOWCOMPOSITIONATTRIBDATA struct { + Attrib WINDOWCOMPOSITIONATTRIB + PvData unsafe.Pointer + CbData uintptr +} + +var ( + uxtheme = syscall.NewLazyDLL("uxtheme.dll") + procSetWindowTheme = uxtheme.NewProc("SetWindowTheme") + procOpenThemeData = uxtheme.NewProc("OpenThemeData") + procCloseThemeData = uxtheme.NewProc("CloseThemeData") + procDrawThemeBackground = uxtheme.NewProc("DrawThemeBackground") + procAllowDarkModeForApplication = uxtheme.NewProc("AllowDarkModeForApp") + procDrawThemeTextEx = uxtheme.NewProc("DrawThemeTextEx") +) + +type PreferredAppMode = int32 + +const ( + PreferredAppModeDefault PreferredAppMode = iota + PreferredAppModeAllowDark + PreferredAppModeForceDark + PreferredAppModeForceLight + PreferredAppModeMax +) + +var ( + AllowDarkModeForWindow func(hwnd HWND, allow bool) uintptr + SetPreferredAppMode func(mode int32) uintptr + FlushMenuThemes func() + RefreshImmersiveColorPolicyState func() + ShouldAppsUseDarkMode func() bool +) + +func init() { + if IsWindowsVersionAtLeast(10, 0, 18334) { + // AllowDarkModeForWindow is only available on Windows 10+ + localUXTheme, err := windows.LoadLibrary("uxtheme.dll") + if err == nil { + procAllowDarkModeForWindow, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(133)) + if err == nil { + AllowDarkModeForWindow = func(hwnd HWND, allow bool) uintptr { + var allowInt int32 + if allow { + allowInt = 1 + } + ret, _, _ := syscall.SyscallN(procAllowDarkModeForWindow, uintptr(allowInt)) + return ret + } + } + + // Add ShouldAppsUseDarkMode + procShouldAppsUseDarkMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(132)) + if err == nil { + ShouldAppsUseDarkMode = func() bool { + ret, _, _ := syscall.SyscallN(procShouldAppsUseDarkMode) + return ret != 0 + } + } + + // SetPreferredAppMode is only available on Windows 10+ + procSetPreferredAppMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(135)) + if err == nil { + SetPreferredAppMode = func(mode int32) uintptr { + ret, _, _ := syscall.SyscallN(procSetPreferredAppMode, uintptr(mode)) + return ret + } + } + + // Add FlushMenuThemes + procFlushMenuThemesAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(136)) + if err == nil { + FlushMenuThemes = func() { + syscall.SyscallN(procFlushMenuThemesAddr) + } + } + + // Add RefreshImmersiveColorPolicyState + procRefreshImmersiveColorPolicyStateAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(104)) + if err == nil { + RefreshImmersiveColorPolicyState = func() { + syscall.SyscallN(procRefreshImmersiveColorPolicyStateAddr) + } + } + + // Initialize dark mode + if SetPreferredAppMode != nil { + SetPreferredAppMode(PreferredAppModeAllowDark) + if RefreshImmersiveColorPolicyState != nil { + RefreshImmersiveColorPolicyState() + } + } + + windows.FreeLibrary(localUXTheme) + } + } +} + +func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) { + ret, _, err := procDwmSetWindowAttribute.Call( + hwnd, + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + if ret != 0 { + _ = err + // println(err.Error()) + } +} + +func SupportsThemes() bool { + // We can't support Windows versions before 17763 + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsCustomThemes() bool { + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsBackdropTypes() bool { + return IsWindowsVersionAtLeast(10, 0, 22621) +} + +func SupportsImmersiveDarkMode() bool { + return IsWindowsVersionAtLeast(10, 0, 18985) +} + +func SetMenuTheme(hwnd uintptr, useDarkMode bool) { + if !SupportsThemes() { + return + } + + // Check if dark mode is supported and enabled + if useDarkMode && ShouldAppsUseDarkMode != nil && !ShouldAppsUseDarkMode() { + useDarkMode = false + } + + // Set the window theme + themeName := "Explorer" + if useDarkMode { + themeName = "DarkMode_Explorer" + } + procSetWindowTheme.Call(HWND(hwnd), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(themeName))), 0) + + // Update the theme state + if RefreshImmersiveColorPolicyState != nil { + RefreshImmersiveColorPolicyState() + } + + // Flush menu themes to force a refresh + if FlushMenuThemes != nil { + FlushMenuThemes() + } + + // Set dark mode for the window + if AllowDarkModeForWindow != nil { + AllowDarkModeForWindow(HWND(hwnd), useDarkMode) + } + + // Force a redraw + InvalidateRect(HWND(hwnd), nil, true) +} + +func SetTheme(hwnd uintptr, useDarkMode bool) { + if SupportsThemes() { + attr := DwmwaUseImmersiveDarkModeBefore20h1 + if SupportsImmersiveDarkMode() { + attr = DwmwaUseImmersiveDarkMode + } + var winDark int32 + if useDarkMode { + winDark = 1 + } + dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark)) + SetMenuTheme(hwnd, useDarkMode) + } +} + +func EnableTranslucency(hwnd uintptr, backdrop uint32) { + dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop)) +} + +func SetTitleBarColour(hwnd uintptr, titleBarColour uint32) { + // Debug: Print the color value being set + // fmt.Printf("Setting titlebar color to: 0x%08X (RGB: %d, %d, %d)\n", titleBarColour, titleBarColour&0xFF, (titleBarColour>>8)&0xFF, (titleBarColour>>16)&0xFF) + dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour)) +} + +func SetTitleTextColour(hwnd uintptr, titleTextColour uint32) { + dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour)) +} + +func SetBorderColour(hwnd uintptr, titleBorderColour uint32) { + dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour)) +} + +func IsCurrentlyDarkMode() bool { + key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE) + if err != nil { + return false + } + defer key.Close() + + AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme") + if err != nil { + return false + } + return AppsUseLightTheme == 0 +} + +type highContrast struct { + CbSize uint32 + DwFlags uint32 + LpszDefaultScheme *int16 +} + +func IsCurrentlyHighContrastMode() bool { + var result highContrast + result.CbSize = uint32(unsafe.Sizeof(result)) + res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0) + if res == 0 { + _ = err + return false + } + r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON + return r +} + +func GetAccentColor() (string, error) { + key, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\DWM`, registry.QUERY_VALUE) + if err != nil { + return "", err + } + defer key.Close() + + accentColor, _, err := key.GetIntegerValue("AccentColor") + if err != nil { + return "", err + } + + // Extract RGB components from ABGR format (Alpha, Blue, Green, Red) + red := uint8(accentColor & 0xFF) + green := uint8((accentColor >> 8) & 0xFF) + blue := uint8((accentColor >> 16) & 0xFF) + + return fmt.Sprintf("rgb(%d,%d,%d)", red, green, blue), nil +} + +// OpenThemeData opens theme data for a window and its class +func OpenThemeData(hwnd HWND, pszClassList string) HTHEME { + ret, _, _ := procOpenThemeData.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(pszClassList)))) + return HTHEME(ret) +} + +// CloseThemeData closes theme data handle +func CloseThemeData(hTheme HTHEME) error { + ret, _, err := procCloseThemeData.Call(uintptr(hTheme)) + if ret != 0 { + return err + } + return nil +} + +// DrawThemeTextEx draws theme text with extended options +func DrawThemeTextEx(hTheme HTHEME, hdc HDC, iPartId int32, iStateId int32, pszText []uint16, cchText int32, dwTextFlags uint32, pRect *RECT, pOptions *DTTOPTS) error { + ret, _, err := procDrawThemeTextEx.Call( + uintptr(hTheme), + uintptr(hdc), + uintptr(iPartId), + uintptr(iStateId), + uintptr(unsafe.Pointer(&pszText[0])), + uintptr(cchText), + uintptr(dwTextFlags), + uintptr(unsafe.Pointer(pRect)), + uintptr(unsafe.Pointer(pOptions))) + if ret != 0 { + return err + } + return nil +} diff --git a/v3/pkg/w32/timer.go b/v3/pkg/w32/timer.go new file mode 100644 index 000000000..c95253e53 --- /dev/null +++ b/v3/pkg/w32/timer.go @@ -0,0 +1,19 @@ +//go:build windows + +package w32 + +func SetTimer(hwnd HWND, nIDEvent uintptr, uElapse uint32, lpTimerFunc uintptr) uintptr { + ret, _, _ := procSetTimer.Call( + uintptr(hwnd), + nIDEvent, + uintptr(uElapse), + lpTimerFunc) + return ret +} + +func KillTimer(hwnd HWND, nIDEvent uintptr) bool { + ret, _, _ := procKillTimer.Call( + uintptr(hwnd), + nIDEvent) + return ret != 0 +} diff --git a/v3/pkg/w32/toolbar.go b/v3/pkg/w32/toolbar.go new file mode 100644 index 000000000..ac9261fc4 --- /dev/null +++ b/v3/pkg/w32/toolbar.go @@ -0,0 +1,216 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +// ToolBar messages +const ( + TB_ENABLEBUTTON = WM_USER + 1 + TB_CHECKBUTTON = WM_USER + 2 + TB_PRESSBUTTON = WM_USER + 3 + TB_HIDEBUTTON = WM_USER + 4 + TB_INDETERMINATE = WM_USER + 5 + TB_MARKBUTTON = WM_USER + 6 + TB_ISBUTTONENABLED = WM_USER + 9 + TB_ISBUTTONCHECKED = WM_USER + 10 + TB_ISBUTTONPRESSED = WM_USER + 11 + TB_ISBUTTONHIDDEN = WM_USER + 12 + TB_ISBUTTONINDETERMINATE = WM_USER + 13 + TB_ISBUTTONHIGHLIGHTED = WM_USER + 14 + TB_SETSTATE = WM_USER + 17 + TB_GETSTATE = WM_USER + 18 + TB_ADDBITMAP = WM_USER + 19 + TB_DELETEBUTTON = WM_USER + 22 + TB_GETBUTTON = WM_USER + 23 + TB_BUTTONCOUNT = WM_USER + 24 + TB_COMMANDTOINDEX = WM_USER + 25 + TB_SAVERESTORE = WM_USER + 76 + TB_CUSTOMIZE = WM_USER + 27 + TB_ADDSTRING = WM_USER + 77 + TB_GETITEMRECT = WM_USER + 29 + TB_BUTTONSTRUCTSIZE = WM_USER + 30 + TB_SETBUTTONSIZE = WM_USER + 31 + TB_SETBITMAPSIZE = WM_USER + 32 + TB_AUTOSIZE = WM_USER + 33 + TB_GETTOOLTIPS = WM_USER + 35 + TB_SETTOOLTIPS = WM_USER + 36 + TB_SETPARENT = WM_USER + 37 + TB_SETROWS = WM_USER + 39 + TB_GETROWS = WM_USER + 40 + TB_GETBITMAPFLAGS = WM_USER + 41 + TB_SETCMDID = WM_USER + 42 + TB_CHANGEBITMAP = WM_USER + 43 + TB_GETBITMAP = WM_USER + 44 + TB_GETBUTTONTEXT = WM_USER + 75 + TB_REPLACEBITMAP = WM_USER + 46 + TB_GETBUTTONSIZE = WM_USER + 58 + TB_SETBUTTONWIDTH = WM_USER + 59 + TB_SETINDENT = WM_USER + 47 + TB_SETIMAGELIST = WM_USER + 48 + TB_GETIMAGELIST = WM_USER + 49 + TB_LOADIMAGES = WM_USER + 50 + TB_GETRECT = WM_USER + 51 + TB_SETHOTIMAGELIST = WM_USER + 52 + TB_GETHOTIMAGELIST = WM_USER + 53 + TB_SETDISABLEDIMAGELIST = WM_USER + 54 + TB_GETDISABLEDIMAGELIST = WM_USER + 55 + TB_SETSTYLE = WM_USER + 56 + TB_GETSTYLE = WM_USER + 57 + TB_SETMAXTEXTROWS = WM_USER + 60 + TB_GETTEXTROWS = WM_USER + 61 + TB_GETOBJECT = WM_USER + 62 + TB_GETBUTTONINFO = WM_USER + 63 + TB_SETBUTTONINFO = WM_USER + 64 + TB_INSERTBUTTON = WM_USER + 67 + TB_ADDBUTTONS = WM_USER + 68 + TB_HITTEST = WM_USER + 69 + TB_SETDRAWTEXTFLAGS = WM_USER + 70 + TB_GETHOTITEM = WM_USER + 71 + TB_SETHOTITEM = WM_USER + 72 + TB_SETANCHORHIGHLIGHT = WM_USER + 73 + TB_GETANCHORHIGHLIGHT = WM_USER + 74 + TB_GETINSERTMARK = WM_USER + 79 + TB_SETINSERTMARK = WM_USER + 80 + TB_INSERTMARKHITTEST = WM_USER + 81 + TB_MOVEBUTTON = WM_USER + 82 + TB_GETMAXSIZE = WM_USER + 83 + TB_SETEXTENDEDSTYLE = WM_USER + 84 + TB_GETEXTENDEDSTYLE = WM_USER + 85 + TB_GETPADDING = WM_USER + 86 + TB_SETPADDING = WM_USER + 87 + TB_SETINSERTMARKCOLOR = WM_USER + 88 + TB_GETINSERTMARKCOLOR = WM_USER + 89 + TB_MAPACCELERATOR = WM_USER + 90 + TB_GETSTRING = WM_USER + 91 + TB_SETCOLORSCHEME = CCM_SETCOLORSCHEME + TB_GETCOLORSCHEME = CCM_GETCOLORSCHEME + TB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT + TB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT +) + +// ToolBar notifications +const ( + TBN_FIRST = -700 + TBN_DROPDOWN = TBN_FIRST - 10 +) + +// TBN_DROPDOWN return codes +const ( + TBDDRET_DEFAULT = 0 + TBDDRET_NODEFAULT = 1 + TBDDRET_TREATPRESSED = 2 +) + +// ToolBar state constants +const ( + TBSTATE_CHECKED = 1 + TBSTATE_PRESSED = 2 + TBSTATE_ENABLED = 4 + TBSTATE_HIDDEN = 8 + TBSTATE_INDETERMINATE = 16 + TBSTATE_WRAP = 32 + TBSTATE_ELLIPSES = 0x40 + TBSTATE_MARKED = 0x0080 +) + +// ToolBar style constants +const ( + TBSTYLE_BUTTON = 0 + TBSTYLE_SEP = 1 + TBSTYLE_CHECK = 2 + TBSTYLE_GROUP = 4 + TBSTYLE_CHECKGROUP = TBSTYLE_GROUP | TBSTYLE_CHECK + TBSTYLE_DROPDOWN = 8 + TBSTYLE_AUTOSIZE = 16 + TBSTYLE_NOPREFIX = 32 + TBSTYLE_TOOLTIPS = 256 + TBSTYLE_WRAPABLE = 512 + TBSTYLE_ALTDRAG = 1024 + TBSTYLE_FLAT = 2048 + TBSTYLE_LIST = 4096 + TBSTYLE_CUSTOMERASE = 8192 + TBSTYLE_REGISTERDROP = 0x4000 + TBSTYLE_TRANSPARENT = 0x8000 +) + +// ToolBar extended style constants +const ( + TBSTYLE_EX_DRAWDDARROWS = 0x00000001 + TBSTYLE_EX_MIXEDBUTTONS = 8 + TBSTYLE_EX_HIDECLIPPEDBUTTONS = 16 + TBSTYLE_EX_DOUBLEBUFFER = 0x80 +) + +// ToolBar button style constants +const ( + BTNS_BUTTON = TBSTYLE_BUTTON + BTNS_SEP = TBSTYLE_SEP + BTNS_CHECK = TBSTYLE_CHECK + BTNS_GROUP = TBSTYLE_GROUP + BTNS_CHECKGROUP = TBSTYLE_CHECKGROUP + BTNS_DROPDOWN = TBSTYLE_DROPDOWN + BTNS_AUTOSIZE = TBSTYLE_AUTOSIZE + BTNS_NOPREFIX = TBSTYLE_NOPREFIX + BTNS_WHOLEDROPDOWN = 0x0080 + BTNS_SHOWTEXT = 0x0040 +) + +// TBBUTTONINFO mask flags +const ( + TBIF_IMAGE = 0x00000001 + TBIF_TEXT = 0x00000002 + TBIF_STATE = 0x00000004 + TBIF_STYLE = 0x00000008 + TBIF_LPARAM = 0x00000010 + TBIF_COMMAND = 0x00000020 + TBIF_SIZE = 0x00000040 + TBIF_BYINDEX = 0x80000000 +) + +type NMMOUSE struct { + Hdr NMHDR + DwItemSpec uintptr + DwItemData uintptr + Pt POINT + DwHitInfo uintptr +} + +type NMTOOLBAR struct { + Hdr NMHDR + IItem int32 + TbButton TBBUTTON + CchText int32 + PszText *uint16 + RcButton RECT +} + +type TBBUTTON struct { + IBitmap int32 + IdCommand int32 + FsState byte + FsStyle byte + //#ifdef _WIN64 + // BYTE bReserved[6] // padding for alignment + //#elif defined(_WIN32) + BReserved [2]byte // padding for alignment + //#endif + DwData uintptr + IString uintptr +} + +type TBBUTTONINFO struct { + CbSize uint32 + DwMask uint32 + IdCommand int32 + IImage int32 + FsState byte + FsStyle byte + Cx uint16 + LParam uintptr + PszText uintptr + CchText int32 +} diff --git a/v3/pkg/w32/typedef.go b/v3/pkg/w32/typedef.go new file mode 100644 index 000000000..cf1c13c09 --- /dev/null +++ b/v3/pkg/w32/typedef.go @@ -0,0 +1,1110 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "unsafe" + + "golang.org/x/sys/windows" +) + +// From MSDN: Windows Data Types +// http://msdn.microsoft.com/en-us/library/s3f49ktz.aspx +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751.aspx +// ATOM WORD +// BOOL int32 +// BOOLEAN byte +// BYTE byte +// CCHAR int8 +// CHAR int8 +// COLORREF DWORD +// DWORD uint32 +// DWORDLONG ULONGLONG +// DWORD_PTR ULONG_PTR +// DWORD32 uint32 +// DWORD64 uint64 +// FLOAT float32 +// HACCEL HANDLE +// HALF_PTR struct{} // ??? +// HANDLE PVOID +// HBITMAP HANDLE +// HBRUSH HANDLE +// HCOLORSPACE HANDLE +// HCONV HANDLE +// HCONVLIST HANDLE +// HCURSOR HANDLE +// HDC HANDLE +// HDDEDATA HANDLE +// HDESK HANDLE +// HDROP HANDLE +// HDWP HANDLE +// HENHMETAFILE HANDLE +// HFILE HANDLE +// HFONT HANDLE +// HGDIOBJ HANDLE +// HGLOBAL HANDLE +// HHOOK HANDLE +// HICON HANDLE +// HINSTANCE HANDLE +// HKEY HANDLE +// HKL HANDLE +// HLOCAL HANDLE +// HMENU HANDLE +// HMETAFILE HANDLE +// HMODULE HANDLE +// HPALETTE HANDLE +// HPEN HANDLE +// HRESULT int32 +// HRGN HANDLE +// HSZ HANDLE +// HWINSTA HANDLE +// HWND HANDLE +// INT int32 +// INT_PTR uintptr +// INT8 int8 +// INT16 int16 +// INT32 int32 +// INT64 int64 +// LANGID WORD +// LCID DWORD +// LCTYPE DWORD +// LGRPID DWORD +// LONG int32 +// LONGLONG int64 +// LONG_PTR uintptr +// LONG32 int32 +// LONG64 int64 +// LPARAM LONG_PTR +// LPBOOL *BOOL +// LPBYTE *BYTE +// LPCOLORREF *COLORREF +// LPCSTR *int8 +// LPCTSTR LPCWSTR +// LPCVOID unsafe.Pointer +// LPCWSTR *WCHAR +// LPDWORD *DWORD +// LPHANDLE *HANDLE +// LPINT *INT +// LPLONG *LONG +// LPSTR *CHAR +// LPTSTR LPWSTR +// LPVOID unsafe.Pointer +// LPWORD *WORD +// LPWSTR *WCHAR +// LRESULT LONG_PTR +// PBOOL *BOOL +// PBOOLEAN *BOOLEAN +// PBYTE *BYTE +// PCHAR *CHAR +// PCSTR *CHAR +// PCTSTR PCWSTR +// PCWSTR *WCHAR +// PDWORD *DWORD +// PDWORDLONG *DWORDLONG +// PDWORD_PTR *DWORD_PTR +// PDWORD32 *DWORD32 +// PDWORD64 *DWORD64 +// PFLOAT *FLOAT +// PHALF_PTR *HALF_PTR +// PHANDLE *HANDLE +// PHKEY *HKEY +// PINT_PTR *INT_PTR +// PINT8 *INT8 +// PINT16 *INT16 +// PINT32 *INT32 +// PINT64 *INT64 +// PLCID *LCID +// PLONG *LONG +// PLONGLONG *LONGLONG +// PLONG_PTR *LONG_PTR +// PLONG32 *LONG32 +// PLONG64 *LONG64 +// POINTER_32 struct{} // ??? +// POINTER_64 struct{} // ??? +// POINTER_SIGNED uintptr +// POINTER_UNSIGNED uintptr +// PSHORT *SHORT +// PSIZE_T *SIZE_T +// PSSIZE_T *SSIZE_T +// PSTR *CHAR +// PTBYTE *TBYTE +// PTCHAR *TCHAR +// PTSTR PWSTR +// PUCHAR *UCHAR +// PUHALF_PTR *UHALF_PTR +// PUINT *UINT +// PUINT_PTR *UINT_PTR +// PUINT8 *UINT8 +// PUINT16 *UINT16 +// PUINT32 *UINT32 +// PUINT64 *UINT64 +// PULONG *ULONG +// PULONGLONG *ULONGLONG +// PULONG_PTR *ULONG_PTR +// PULONG32 *ULONG32 +// PULONG64 *ULONG64 +// PUSHORT *USHORT +// PVOID unsafe.Pointer +// PWCHAR *WCHAR +// PWORD *WORD +// PWSTR *WCHAR +// QWORD uint64 +// SC_HANDLE HANDLE +// SC_LOCK LPVOID +// SERVICE_STATUS_HANDLE HANDLE +// SHORT int16 +// SIZE_T ULONG_PTR +// SSIZE_T LONG_PTR +// TBYTE WCHAR +// TCHAR WCHAR +// UCHAR uint8 +// UHALF_PTR struct{} // ??? +// UINT uint32 +// UINT_PTR uintptr +// UINT8 uint8 +// UINT16 uint16 +// UINT32 uint32 +// UINT64 uint64 +// ULONG uint32 +// ULONGLONG uint64 +// ULONG_PTR uintptr +// ULONG32 uint32 +// ULONG64 uint64 +// USHORT uint16 +// USN LONGLONG +// WCHAR uint16 +// WORD uint16 +// WPARAM UINT_PTR +type ( + ATOM = uint16 + BOOL = int32 + COLORREF = uint32 + DWM_FRAME_COUNT = uint64 + WORD = uint16 + DWORD = uint32 + HACCEL = HANDLE + HANDLE = uintptr + HBITMAP = HANDLE + HBRUSH = HANDLE + HCURSOR = HANDLE + HDC = HANDLE + HDROP = HANDLE + HDWP = HANDLE + HENHMETAFILE = HANDLE + HFONT = HANDLE + HGDIOBJ = HANDLE + HGLOBAL = HANDLE + HGLRC = HANDLE + HHOOK = HANDLE + HICON = HANDLE + HIMAGELIST = HANDLE + HINSTANCE = HANDLE + HKEY = HANDLE + HKL = HANDLE + HMENU = HANDLE + HMODULE = HANDLE + HMONITOR = HANDLE + HPEN = HANDLE + HRESULT = int32 + HRGN = HANDLE + HRSRC = HANDLE + HTHUMBNAIL = HANDLE + HWND = HANDLE + LPARAM = uintptr + LPCVOID = unsafe.Pointer + LRESULT = uintptr + PVOID = unsafe.Pointer + QPC_TIME = uint64 + ULONG_PTR = uintptr + SIZE_T = ULONG_PTR + WPARAM = uintptr + UINT = uint +) + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162805.aspx +type POINT struct { + X, Y int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897.aspx +type RECT struct { + Left, Top, Right, Bottom int32 +} + +func (r *RECT) String() string { + return fmt.Sprintf("RECT (%p): Left: %d, Top: %d, Right: %d, Bottom: %d", r, r.Left, r.Top, r.Right, r.Bottom) +} + +func RectToPoints(rect *RECT) []POINT { + return []POINT{ + {X: rect.Left, Y: rect.Top}, // Top-left + {X: rect.Right, Y: rect.Bottom}, // Bottom-right + } +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx +type WNDCLASSEX struct { + Size uint32 + Style uint32 + WndProc uintptr + ClsExtra int32 + WndExtra int32 + Instance HINSTANCE + Icon HICON + Cursor HCURSOR + Background HBRUSH + MenuName *uint16 + ClassName *uint16 + IconSm HICON +} + +type TPMPARAMS struct { + CbSize uint32 + RcExclude RECT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx +type MSG struct { + Hwnd HWND + Message uint32 + WParam uintptr + LParam uintptr + Time uint32 + Pt POINT +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo +type MINMAXINFO struct { + PtReserved POINT + PtMaxSize POINT + PtMaxPosition POINT + PtMinTrackSize POINT + PtMaxTrackSize POINT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145037.aspx +type LOGFONT struct { + Height int32 + Width int32 + Escapement int32 + Orientation int32 + Weight int32 + Italic byte + Underline byte + StrikeOut byte + CharSet byte + OutPrecision byte + ClipPrecision byte + Quality byte + PitchAndFamily byte + FaceName [LF_FACESIZE]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646839.aspx +type OPENFILENAME struct { + StructSize uint32 + Owner HWND + Instance HINSTANCE + Filter *uint16 + CustomFilter *uint16 + MaxCustomFilter uint32 + FilterIndex uint32 + File *uint16 + MaxFile uint32 + FileTitle *uint16 + MaxFileTitle uint32 + InitialDir *uint16 + Title *uint16 + Flags uint32 + FileOffset uint16 + FileExtension uint16 + DefExt *uint16 + CustData uintptr + FnHook uintptr + TemplateName *uint16 + PvReserved unsafe.Pointer + DwReserved uint32 + FlagsEx uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773205.aspx +type BROWSEINFO struct { + Owner HWND + Root *uint16 + DisplayName *uint16 + Title *uint16 + Flags uint32 + CallbackFunc uintptr + LParam uintptr + Image int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221627.aspx +type VARIANT struct { + VT uint16 // 2 + WReserved1 uint16 // 4 + WReserved2 uint16 // 6 + WReserved3 uint16 // 8 + Val int64 // 16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221416.aspx +type DISPPARAMS struct { + Rgvarg uintptr + RgdispidNamedArgs uintptr + CArgs uint32 + CNamedArgs uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms221133.aspx +type EXCEPINFO struct { + WCode uint16 + WReserved uint16 + BstrSource *uint16 + BstrDescription *uint16 + BstrHelpFile *uint16 + DwHelpContext uint32 + PvReserved uintptr + PfnDeferredFillIn uintptr + Scode int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145035.aspx +type LOGBRUSH struct { + LbStyle uint32 + LbColor COLORREF + LbHatch uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183565.aspx +type DEVMODE struct { + DmDeviceName [CCHDEVICENAME]uint16 + DmSpecVersion uint16 + DmDriverVersion uint16 + DmSize uint16 + DmDriverExtra uint16 + DmFields uint32 + DmOrientation int16 + DmPaperSize int16 + DmPaperLength int16 + DmPaperWidth int16 + DmScale int16 + DmCopies int16 + DmDefaultSource int16 + DmPrintQuality int16 + DmColor int16 + DmDuplex int16 + DmYResolution int16 + DmTTOption int16 + DmCollate int16 + DmFormName [CCHFORMNAME]uint16 + DmLogPixels uint16 + DmBitsPerPel uint32 + DmPelsWidth uint32 + DmPelsHeight uint32 + DmDisplayFlags uint32 + DmDisplayFrequency uint32 + DmICMMethod uint32 + DmICMIntent uint32 + DmMediaType uint32 + DmDitherType uint32 + DmReserved1 uint32 + DmReserved2 uint32 + DmPanningWidth uint32 + DmPanningHeight uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx +type BITMAPINFOHEADER struct { + BiSize uint32 + BiWidth int32 + BiHeight int32 + BiPlanes uint16 + BiBitCount uint16 + BiCompression uint32 + BiSizeImage uint32 + BiXPelsPerMeter int32 + BiYPelsPerMeter int32 + BiClrUsed uint32 + BiClrImportant uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx +type RGBQUAD struct { + RgbBlue byte + RgbGreen byte + RgbRed byte + RgbReserved byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx +type BITMAPINFO struct { + BmiHeader BITMAPINFOHEADER + BmiColors *RGBQUAD +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx +type BITMAP struct { + BmType int32 + BmWidth int32 + BmHeight int32 + BmWidthBytes int32 + BmPlanes uint16 + BmBitsPixel uint16 + BmBits unsafe.Pointer +} + +type BLENDFUNCTION struct { + BlendOp byte + BlendFlags byte + SourceConstantAlpha byte + AlphaFormat byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183567.aspx +type DIBSECTION struct { + DsBm BITMAP + DsBmih BITMAPINFOHEADER + DsBitfields [3]uint32 + DshSection HANDLE + DsOffset uint32 +} + +type MSGBOXPARAMS struct { + cbSize uint32 + hwndOwner HWND + hInstance HANDLE + lpszText *uint16 + lpszCaption *uint16 + dwStyle uint32 + lpszIcon *uint16 + dwContextHelp uintptr + lpfnMsgBoxCallback uintptr + dwLanguageId uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162607.aspx +type ENHMETAHEADER struct { + IType uint32 + NSize uint32 + RclBounds RECT + RclFrame RECT + DSignature uint32 + NVersion uint32 + NBytes uint32 + NRecords uint32 + NHandles uint16 + SReserved uint16 + NDescription uint32 + OffDescription uint32 + NPalEntries uint32 + SzlDevice SIZE + SzlMillimeters SIZE + CbPixelFormat uint32 + OffPixelFormat uint32 + BOpenGL uint32 + SzlMicrometers SIZE +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145106.aspx +type SIZE struct { + CX, CY int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145132.aspx +type TEXTMETRIC struct { + TmHeight int32 + TmAscent int32 + TmDescent int32 + TmInternalLeading int32 + TmExternalLeading int32 + TmAveCharWidth int32 + TmMaxCharWidth int32 + TmWeight int32 + TmOverhang int32 + TmDigitizedAspectX int32 + TmDigitizedAspectY int32 + TmFirstChar uint16 + TmLastChar uint16 + TmDefaultChar uint16 + TmBreakChar uint16 + TmItalic byte + TmUnderlined byte + TmStruckOut byte + TmPitchAndFamily byte + TmCharSet byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183574.aspx +type DOCINFO struct { + CbSize int32 + LpszDocName *uint16 + LpszOutput *uint16 + LpszDatatype *uint16 + FwType uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775514.aspx +type NMHDR struct { + HwndFrom HWND + IdFrom uintptr + Code uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774743.aspx +type LVCOLUMN struct { + Mask uint32 + Fmt int32 + Cx int32 + PszText *uint16 + CchTextMax int32 + ISubItem int32 + IImage int32 + IOrder int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774760.aspx +type LVITEM struct { + Mask uint32 + IItem int32 + ISubItem int32 + State uint32 + StateMask uint32 + PszText *uint16 + CchTextMax int32 + IImage int32 + LParam uintptr + IIndent int32 + IGroupId int32 + CColumns uint32 + PuColumns uint32 +} + +type LVFINDINFO struct { + Flags uint32 + PszText *uint16 + LParam uintptr + Pt POINT + VkDirection uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774754.aspx +type LVHITTESTINFO struct { + Pt POINT + Flags uint32 + IItem int32 + ISubItem int32 + IGroup int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774771.aspx +type NMITEMACTIVATE struct { + Hdr NMHDR + IItem int32 + ISubItem int32 + UNewState uint32 + UOldState uint32 + UChanged uint32 + PtAction POINT + LParam uintptr + UKeyFlags uint32 +} + +type NMLVKEYDOWN struct { + Hdr NMHDR + WVKey uint16 + Flags uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774773.aspx +type NMLISTVIEW struct { + Hdr NMHDR + IItem int32 + ISubItem int32 + UNewState uint32 + UOldState uint32 + UChanged uint32 + PtAction POINT + LParam uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb774780.aspx +type NMLVDISPINFO struct { + Hdr NMHDR + Item LVITEM +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507.aspx +type INITCOMMONCONTROLSEX struct { + DwSize uint32 + DwICC uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb760256.aspx +type TOOLINFO struct { + CbSize uint32 + UFlags uint32 + Hwnd HWND + UId uintptr + Rect RECT + Hinst HINSTANCE + LpszText *uint16 + LParam uintptr + LpReserved unsafe.Pointer +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645604.aspx +type TRACKMOUSEEVENT struct { + CbSize uint32 + DwFlags uint32 + HwndTrack HWND + DwHoverTime uint32 +} + +type NOTIFYICONDATA struct { + CbSize uint32 + HWnd HWND + UID uint32 + UFlags uint32 + UCallbackMessage uint32 + HIcon HICON + SzTip [128]uint16 + DwState uint32 + DwStateMask uint32 + SzInfo [256]uint16 + UVersion uint32 + SzInfoTitle [64]uint16 + DwInfoFlags uint32 + GuidItem windows.GUID + HBalloonIcon HICON +} + +const SPI_GETNOTIFYWINDOWRECT = 0x0040 +const SPI_SETWORKAREA = 0x002F + +// Taskbar constants +const ABM_GETTASKBARPOS = 0x00000005 +const ABM_GETSTATE = 0x00000004 +const ABM_GETAUTOHIDEBAR = 0x00000007 +const ABM_SETSTATE = 0x0000000a +const ABM_SETAUTOHIDEBAR = 0x00000008 +const ABM_WINDOWPOSCHANGED = 0x00000009 +const ABM_SETPOS = 0x00000003 + +const ABE_LEFT = 0 +const ABE_TOP = 1 +const ABE_RIGHT = 2 +const ABE_BOTTOM = 3 + +type NOTIFYICONIDENTIFIER struct { + CbSize uint32 + HWnd HWND + UId uint32 + GuidItem windows.GUID +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms534067.aspx +type GdiplusStartupInput struct { + GdiplusVersion uint32 + DebugEventCallback uintptr + SuppressBackgroundThread BOOL + SuppressExternalCodecs BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms534068.aspx +type GdiplusStartupOutput struct { + NotificationHook uintptr + NotificationUnhook uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162768.aspx +type PAINTSTRUCT struct { + Hdc HDC + FErase BOOL + RcPaint RECT + FRestore BOOL + FIncUpdate BOOL + RgbReserved [32]byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363646.aspx +type EVENTLOGRECORD struct { + Length uint32 + Reserved uint32 + RecordNumber uint32 + TimeGenerated uint32 + TimeWritten uint32 + EventID uint32 + EventType uint16 + NumStrings uint16 + EventCategory uint16 + ReservedFlags uint16 + ClosingRecordNumber uint32 + StringOffset uint32 + UserSidLength uint32 + UserSidOffset uint32 + DataLength uint32 + DataOffset uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms685996.aspx +type SERVICE_STATUS struct { + DwServiceType uint32 + DwCurrentState uint32 + DwControlsAccepted uint32 + DwWin32ExitCode uint32 + DwServiceSpecificExitCode uint32 + DwCheckPoint uint32 + DwWaitHint uint32 +} + +/* ------------------------- + Undocumented API +------------------------- */ + +type ACCENT_STATE DWORD + +const ( + ACCENT_DISABLED ACCENT_STATE = 0 + ACCENT_ENABLE_GRADIENT ACCENT_STATE = 1 + ACCENT_ENABLE_TRANSPARENTGRADIENT ACCENT_STATE = 2 + ACCENT_ENABLE_BLURBEHIND ACCENT_STATE = 3 + ACCENT_ENABLE_ACRYLICBLURBEHIND ACCENT_STATE = 4 // RS4 1803 + ACCENT_ENABLE_HOSTBACKDROP ACCENT_STATE = 5 // RS5 1809 + ACCENT_INVALID_STATE ACCENT_STATE = 6 +) + +type ACCENT_POLICY struct { + AccentState ACCENT_STATE + AccentFlags DWORD + GradientColor DWORD + AnimationId DWORD +} + +// ------------------------- + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684225.aspx +type MODULEENTRY32 struct { + Size uint32 + ModuleID uint32 + ProcessID uint32 + GlblcntUsage uint32 + ProccntUsage uint32 + ModBaseAddr *uint8 + ModBaseSize uint32 + HModule HMODULE + SzModule [MAX_MODULE_NAME32 + 1]uint16 + SzExePath [MAX_PATH]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284.aspx +type FILETIME struct { + DwLowDateTime uint32 + DwHighDateTime uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119.aspx +type COORD struct { + X, Y int16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311.aspx +type SMALL_RECT struct { + Left, Top, Right, Bottom int16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093.aspx +type CONSOLE_SCREEN_BUFFER_INFO struct { + DwSize COORD + DwCursorPosition COORD + WAttributes uint16 + SrWindow SMALL_RECT + DwMaximumWindowSize COORD +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx +type MARGINS struct { + CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969500.aspx +type DWM_BLURBEHIND struct { + DwFlags uint32 + fEnable BOOL + hRgnBlur HRGN + fTransitionOnMaximized BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969501.aspx +type DWM_PRESENT_PARAMETERS struct { + cbSize uint32 + fQueue BOOL + cRefreshStart DWM_FRAME_COUNT + cBuffer uint32 + fUseSourceRate BOOL + rateSource UNSIGNED_RATIO + cRefreshesPerFrame uint32 + eSampling DWM_SOURCE_FRAME_SAMPLING +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969502.aspx +type DWM_THUMBNAIL_PROPERTIES struct { + dwFlags uint32 + rcDestination RECT + rcSource RECT + opacity byte + fVisible BOOL + fSourceClientAreaOnly BOOL +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969503.aspx +type DWM_TIMING_INFO struct { + cbSize uint32 + rateRefresh UNSIGNED_RATIO + qpcRefreshPeriod QPC_TIME + rateCompose UNSIGNED_RATIO + qpcVBlank QPC_TIME + cRefresh DWM_FRAME_COUNT + cDXRefresh uint32 + qpcCompose QPC_TIME + cFrame DWM_FRAME_COUNT + cDXPresent uint32 + cRefreshFrame DWM_FRAME_COUNT + cFrameSubmitted DWM_FRAME_COUNT + cDXPresentSubmitted uint32 + cFrameConfirmed DWM_FRAME_COUNT + cDXPresentConfirmed uint32 + cRefreshConfirmed DWM_FRAME_COUNT + cDXRefreshConfirmed uint32 + cFramesLate DWM_FRAME_COUNT + cFramesOutstanding uint32 + cFrameDisplayed DWM_FRAME_COUNT + qpcFrameDisplayed QPC_TIME + cRefreshFrameDisplayed DWM_FRAME_COUNT + cFrameComplete DWM_FRAME_COUNT + qpcFrameComplete QPC_TIME + cFramePending DWM_FRAME_COUNT + qpcFramePending QPC_TIME + cFramesDisplayed DWM_FRAME_COUNT + cFramesComplete DWM_FRAME_COUNT + cFramesPending DWM_FRAME_COUNT + cFramesAvailable DWM_FRAME_COUNT + cFramesDropped DWM_FRAME_COUNT + cFramesMissed DWM_FRAME_COUNT + cRefreshNextDisplayed DWM_FRAME_COUNT + cRefreshNextPresented DWM_FRAME_COUNT + cRefreshesDisplayed DWM_FRAME_COUNT + cRefreshesPresented DWM_FRAME_COUNT + cRefreshStarted DWM_FRAME_COUNT + cPixelsReceived uint64 + cPixelsDrawn uint64 + cBuffersEmpty DWM_FRAME_COUNT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd389402.aspx +type MilMatrix3x2D struct { + S_11, S_12, S_21, S_22 float64 + DX, DY float64 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969505.aspx +type UNSIGNED_RATIO struct { + uiNumerator uint32 + uiDenominator uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms632603.aspx +type CREATESTRUCT struct { + CreateParams uintptr + Instance HINSTANCE + Menu HMENU + Parent HWND + Cy, Cx int32 + Y, X int32 + Style int32 + Name *uint16 + Class *uint16 + dwExStyle uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145065.aspx +type MONITORINFO struct { + CbSize uint32 + RcMonitor RECT + RcWork RECT + DwFlags uint32 +} + +type WINDOWINFO struct { + CbSize DWORD + RcWindow RECT + RcClient RECT + DwStyle DWORD + DwExStyle DWORD + DwWindowStatus DWORD + CxWindowBorders UINT + CyWindowBorders UINT + AtomWindowType ATOM + WCreatorVersion WORD +} + +type MONITOR_DPI_TYPE int32 + +const ( + MDT_EFFECTIVE_DPI MONITOR_DPI_TYPE = 0 + MDT_ANGULAR_DPI MONITOR_DPI_TYPE = 1 + MDT_RAW_DPI MONITOR_DPI_TYPE = 2 + MDT_DEFAULT MONITOR_DPI_TYPE = 0 +) + +func (w *WINDOWINFO) isStyle(style DWORD) bool { + return w.DwStyle&style == style +} + +func (w *WINDOWINFO) IsPopup() bool { + return w.isStyle(WS_POPUP) +} + +func (m *MONITORINFO) Dump() { + fmt.Printf("MONITORINFO (%p)\n", m) + fmt.Printf(" CbSize : %d\n", m.CbSize) + fmt.Printf(" RcMonitor: %s\n", &m.RcMonitor) + fmt.Printf(" RcWork : %s\n", &m.RcWork) + fmt.Printf(" DwFlags : %d\n", m.DwFlags) +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145066.aspx +type MONITORINFOEX struct { + MONITORINFO + SzDevice [CCHDEVICENAME]uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd368826.aspx +type PIXELFORMATDESCRIPTOR struct { + Size uint16 + Version uint16 + DwFlags uint32 + IPixelType byte + ColorBits byte + RedBits, RedShift byte + GreenBits, GreenShift byte + BlueBits, BlueShift byte + AlphaBits, AlphaShift byte + AccumBits byte + AccumRedBits byte + AccumGreenBits byte + AccumBlueBits byte + AccumAlphaBits byte + DepthBits, StencilBits byte + AuxBuffers byte + ILayerType byte + Reserved byte + DwLayerMask uint32 + DwVisibleMask uint32 + DwDamageMask uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx +type INPUT struct { + Type uint32 + Mi MOUSEINPUT + Ki KEYBDINPUT + Hi HARDWAREINPUT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273(v=vs.85).aspx +type MOUSEINPUT struct { + Dx int32 + Dy int32 + MouseData uint32 + DwFlags uint32 + Time uint32 + DwExtraInfo uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646271(v=vs.85).aspx +type KEYBDINPUT struct { + WVk uint16 + WScan uint16 + DwFlags uint32 + Time uint32 + DwExtraInfo uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646269(v=vs.85).aspx +type HARDWAREINPUT struct { + UMsg uint32 + WParamL uint16 + WParamH uint16 +} + +type KbdInput struct { + typ uint32 + ki KEYBDINPUT +} + +type MouseInput struct { + typ uint32 + mi MOUSEINPUT +} + +type HardwareInput struct { + typ uint32 + hi HARDWAREINPUT +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx +type SYSTEMTIME struct { + Year uint16 + Month uint16 + DayOfWeek uint16 + Day uint16 + Hour uint16 + Minute uint16 + Second uint16 + Milliseconds uint16 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644967(v=vs.85).aspx +type KBDLLHOOKSTRUCT struct { + VkCode DWORD + ScanCode DWORD + Flags DWORD + Time DWORD + DwExtraInfo ULONG_PTR +} + +type HOOKPROC func(int, WPARAM, LPARAM) LRESULT + +type WINDOWPLACEMENT struct { + Length uint32 + Flags uint32 + ShowCmd uint32 + PtMinPosition POINT + PtMaxPosition POINT + RcNormalPosition RECT +} + +type SCROLLINFO struct { + CbSize uint32 + FMask uint32 + NMin int32 + NMax int32 + NPage uint32 + NPos int32 + NTrackPos int32 +} + +type FLASHWINFO struct { + CbSize uint32 + Hwnd HWND + DwFlags DWORD + UCount uint32 + DwTimeout DWORD +} diff --git a/v3/pkg/w32/user32.go b/v3/pkg/w32/user32.go new file mode 100644 index 000000000..8988b786e --- /dev/null +++ b/v3/pkg/w32/user32.go @@ -0,0 +1,1494 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + moduser32 = syscall.NewLazyDLL("user32.dll") + + procRegisterClassEx = moduser32.NewProc("RegisterClassExW") + procGetClassName = moduser32.NewProc("GetClassNameW") + procLoadIcon = moduser32.NewProc("LoadIconW") + procLoadCursor = moduser32.NewProc("LoadCursorW") + procShowWindow = moduser32.NewProc("ShowWindow") + procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") + procShowWindowAsync = moduser32.NewProc("ShowWindowAsync") + procIsZoomed = moduser32.NewProc("IsZoomed") + procUpdateWindow = moduser32.NewProc("UpdateWindow") + procCreateWindowEx = moduser32.NewProc("CreateWindowExW") + procAdjustWindowRect = moduser32.NewProc("AdjustWindowRect") + procAdjustWindowRectEx = moduser32.NewProc("AdjustWindowRectEx") + procDestroyWindow = moduser32.NewProc("DestroyWindow") + procDefWindowProc = moduser32.NewProc("DefWindowProcW") + procDefDlgProc = moduser32.NewProc("DefDlgProcW") + procPostQuitMessage = moduser32.NewProc("PostQuitMessage") + procGetMessage = moduser32.NewProc("GetMessageW") + procTranslateMessage = moduser32.NewProc("TranslateMessage") + procDispatchMessage = moduser32.NewProc("DispatchMessageW") + procSendMessage = moduser32.NewProc("SendMessageW") + procPostMessage = moduser32.NewProc("PostMessageW") + procWaitMessage = moduser32.NewProc("WaitMessage") + procSetWindowText = moduser32.NewProc("SetWindowTextW") + procGetWindowTextLength = moduser32.NewProc("GetWindowTextLengthW") + procGetWindowText = moduser32.NewProc("GetWindowTextW") + procGetWindowRect = moduser32.NewProc("GetWindowRect") + procGetWindowInfo = moduser32.NewProc("GetWindowInfo") + procGetWindow = moduser32.NewProc("GetWindow") + procSetWindowCompositionAttribute = moduser32.NewProc("SetWindowCompositionAttribute") + procMoveWindow = moduser32.NewProc("MoveWindow") + procScreenToClient = moduser32.NewProc("ScreenToClient") + procCallWindowProc = moduser32.NewProc("CallWindowProcW") + procSetWindowLong = moduser32.NewProc("SetWindowLongW") + procSetWindowLongPtr = moduser32.NewProc("SetWindowLongW") + procGetWindowLong = moduser32.NewProc("GetWindowLongW") + procGetWindowLongPtr = moduser32.NewProc("GetWindowLongW") + procEnableWindow = moduser32.NewProc("EnableWindow") + procIsWindowEnabled = moduser32.NewProc("IsWindowEnabled") + procIsWindowVisible = moduser32.NewProc("IsWindowVisible") + procSetFocus = moduser32.NewProc("SetFocus") + procGetFocus = moduser32.NewProc("GetFocus") + procSetActiveWindow = moduser32.NewProc("SetActiveWindow") + procSetForegroundWindow = moduser32.NewProc("SetForegroundWindow") + procBringWindowToTop = moduser32.NewProc("BringWindowToTop") + procInvalidateRect = moduser32.NewProc("InvalidateRect") + procGetClientRect = moduser32.NewProc("GetClientRect") + procGetDC = moduser32.NewProc("GetDC") + procReleaseDC = moduser32.NewProc("ReleaseDC") + procSetCapture = moduser32.NewProc("SetCapture") + procReleaseCapture = moduser32.NewProc("ReleaseCapture") + procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") + procMessageBox = moduser32.NewProc("MessageBoxW") + procMessageBoxIndirect = moduser32.NewProc("MessageBoxIndirectW") + procGetSystemMetrics = moduser32.NewProc("GetSystemMetrics") + procPostThreadMessageW = moduser32.NewProc("PostThreadMessageW") + procRegisterWindowMessageA = moduser32.NewProc("RegisterWindowMessageA") + procCopyRect = moduser32.NewProc("CopyRect") + procEqualRect = moduser32.NewProc("EqualRect") + procInflateRect = moduser32.NewProc("InflateRect") + procIntersectRect = moduser32.NewProc("IntersectRect") + procIsRectEmpty = moduser32.NewProc("IsRectEmpty") + procOffsetRect = moduser32.NewProc("OffsetRect") + procPtInRect = moduser32.NewProc("PtInRect") + procSetRect = moduser32.NewProc("SetRect") + procSetRectEmpty = moduser32.NewProc("SetRectEmpty") + procSubtractRect = moduser32.NewProc("SubtractRect") + procUnionRect = moduser32.NewProc("UnionRect") + procCreateDialogParam = moduser32.NewProc("CreateDialogParamW") + procDialogBoxParam = moduser32.NewProc("DialogBoxParamW") + procGetDlgItem = moduser32.NewProc("GetDlgItem") + procDrawIcon = moduser32.NewProc("DrawIcon") + procCreateMenu = moduser32.NewProc("CreateMenu") + procRemoveMenu = moduser32.NewProc("RemoveMenu") + procGetMenuItemPosition = moduser32.NewProc("GetMenuItemPosition") + procDestroyMenu = moduser32.NewProc("DestroyMenu") + procCreatePopupMenu = moduser32.NewProc("CreatePopupMenu") + procCheckMenuRadioItem = moduser32.NewProc("CheckMenuRadioItem") + procCreateIconFromResourceEx = moduser32.NewProc("CreateIconFromResourceEx") + procInsertMenuItem = moduser32.NewProc("InsertMenuItemW") + procCheckMenuItem = moduser32.NewProc("CheckMenuItem") + procClientToScreen = moduser32.NewProc("ClientToScreen") + procIsDialogMessage = moduser32.NewProc("IsDialogMessageW") + procIsWindow = moduser32.NewProc("IsWindow") + procEndDialog = moduser32.NewProc("EndDialog") + procPeekMessage = moduser32.NewProc("PeekMessageW") + procTranslateAccelerator = moduser32.NewProc("TranslateAcceleratorW") + procSetWindowPos = moduser32.NewProc("SetWindowPos") + procFillRect = moduser32.NewProc("FillRect") + procFrameRect = moduser32.NewProc("FrameRect") + procDrawText = moduser32.NewProc("DrawTextW") + procAddClipboardFormatListener = moduser32.NewProc("AddClipboardFormatListener") + procRemoveClipboardFormatListener = moduser32.NewProc("RemoveClipboardFormatListener") + procOpenClipboard = moduser32.NewProc("OpenClipboard") + procCloseClipboard = moduser32.NewProc("CloseClipboard") + procEnumClipboardFormats = moduser32.NewProc("EnumClipboardFormats") + procGetClipboardData = moduser32.NewProc("GetClipboardData") + procSetClipboardData = moduser32.NewProc("SetClipboardData") + procEmptyClipboard = moduser32.NewProc("EmptyClipboard") + procGetClipboardFormatName = moduser32.NewProc("GetClipboardFormatNameW") + procIsClipboardFormatAvailable = moduser32.NewProc("IsClipboardFormatAvailable") + procBeginPaint = moduser32.NewProc("BeginPaint") + procEndPaint = moduser32.NewProc("EndPaint") + procGetKeyboardState = moduser32.NewProc("GetKeyboardState") + procMapVirtualKey = moduser32.NewProc("MapVirtualKeyW") + procMapVirtualKeyEx = moduser32.NewProc("MapVirtualKeyExW") + procGetAsyncKeyState = moduser32.NewProc("GetAsyncKeyState") + procToAscii = moduser32.NewProc("ToAscii") + procSwapMouseButton = moduser32.NewProc("SwapMouseButton") + procGetCursorPos = moduser32.NewProc("GetCursorPos") + procSetCursorPos = moduser32.NewProc("SetCursorPos") + procSetCursor = moduser32.NewProc("SetCursor") + procCreateIcon = moduser32.NewProc("CreateIcon") + procDestroyIcon = moduser32.NewProc("DestroyIcon") + procMonitorFromPoint = moduser32.NewProc("MonitorFromPoint") + procMonitorFromRect = moduser32.NewProc("MonitorFromRect") + procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow") + procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW") + procGetDpiForSystem = moduser32.NewProc("GetDpiForSystem") + procGetDpiForWindow = moduser32.NewProc("GetDpiForWindow") + procSetProcessDPIAware = moduser32.NewProc("SetProcessDPIAware") + procSetProcessDpiAwarenessContext = moduser32.NewProc("SetProcessDpiAwarenessContext") + procEnumDisplayMonitors = moduser32.NewProc("EnumDisplayMonitors") + procEnumDisplayDevices = moduser32.NewProc("EnumDisplayDevicesW") + procEnumDisplaySettings = moduser32.NewProc("EnumDisplaySettingsW") + procEnumDisplaySettingsEx = moduser32.NewProc("EnumDisplaySettingsExW") + procEnumWindows = moduser32.NewProc("EnumWindows") + procEnumChildWindows = moduser32.NewProc("EnumChildWindows") + procChangeDisplaySettingsEx = moduser32.NewProc("ChangeDisplaySettingsExW") + procSetTimer = moduser32.NewProc("SetTimer") + procKillTimer = moduser32.NewProc("KillTimer") + procSendInput = moduser32.NewProc("SendInput") + procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW") + procUnhookWindowsHookEx = moduser32.NewProc("UnhookWindowsHookEx") + procCallNextHookEx = moduser32.NewProc("CallNextHookEx") + procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow") + procUpdateLayeredWindow = moduser32.NewProc("UpdateLayeredWindow") + getDisplayConfig = moduser32.NewProc("GetDisplayConfigBufferSizes") + queryDisplayConfig = moduser32.NewProc("QueryDisplayConfig") + + procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW") + procSetClassLong = moduser32.NewProc("SetClassLongW") + procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW") + + procSetMenu = moduser32.NewProc("SetMenu") + procAppendMenu = moduser32.NewProc("AppendMenuW") + procSetMenuItemInfo = moduser32.NewProc("SetMenuItemInfoW") + procDrawMenuBar = moduser32.NewProc("DrawMenuBar") + procTrackPopupMenu = moduser32.NewProc("TrackPopupMenu") + procTrackPopupMenuEx = moduser32.NewProc("TrackPopupMenuEx") + procGetMenuBarInfo = moduser32.NewProc("GetMenuBarInfo") + procMapWindowPoints = moduser32.NewProc("MapWindowPoints") + procGetKeyState = moduser32.NewProc("GetKeyState") + procGetSysColorBrush = moduser32.NewProc("GetSysColorBrush") + procGetSysColor = moduser32.NewProc("GetSysColor") + + procGetWindowPlacement = moduser32.NewProc("GetWindowPlacement") + procSetWindowPlacement = moduser32.NewProc("SetWindowPlacement") + procGetWindowDC = moduser32.NewProc("GetWindowDC") + + procGetScrollInfo = moduser32.NewProc("GetScrollInfo") + procSetScrollInfo = moduser32.NewProc("SetScrollInfo") + + procFlashWindowEx = moduser32.NewProc("FlashWindowEx") + + procSetMenuItemBitmaps = moduser32.NewProc("SetMenuItemBitmaps") + + procRedrawWindow = moduser32.NewProc("RedrawWindow") + + procRegisterWindowMessageW = moduser32.NewProc("RegisterWindowMessageW") + + mainThread HANDLE +) + +func init() { + runtime.LockOSThread() + mainThread = GetCurrentThreadId() +} + +func GetWindowDC(hwnd HWND) HDC { + ret, _, _ := procGetWindowDC.Call(hwnd) + return ret +} + +func GET_X_LPARAM(lp uintptr) int32 { + return int32(int16(LOWORD(uint32(lp)))) +} + +func GET_Y_LPARAM(lp uintptr) int32 { + return int32(int16(HIWORD(uint32(lp)))) +} + +func RegisterClassEx(wndClassEx *WNDCLASSEX) ATOM { + ret, _, _ := procRegisterClassEx.Call(uintptr(unsafe.Pointer(wndClassEx))) + return ATOM(ret) +} + +func SetMenuItemBitmaps(hMenu HMENU, uPosition, uFlags uint32, hBitmapUnchecked HBITMAP, hBitmapChecked HBITMAP) error { + ret, _, _ := procSetMenuItemBitmaps.Call( + hMenu, + uintptr(uPosition), + uintptr(uFlags), + hBitmapUnchecked, + hBitmapChecked) + + if ret == 0 { + return windows.GetLastError() + } + return nil +} + +func GetDesktopWindow() HWND { + ret, _, _ := procGetDesktopWindow.Call() + return ret +} + +func LoadIcon(instance HINSTANCE, iconName *uint16) HICON { + ret, _, _ := procLoadIcon.Call( + uintptr(instance), + uintptr(unsafe.Pointer(iconName))) + + return HICON(ret) +} + +func LoadIconWithResourceID(instance HINSTANCE, res uint16) HICON { + ret, _, _ := procLoadIcon.Call( + uintptr(instance), + uintptr(res)) + + return HICON(ret) +} + +func LoadCursor(instance HINSTANCE, cursorName *uint16) HCURSOR { + ret, _, _ := procLoadCursor.Call( + uintptr(instance), + uintptr(unsafe.Pointer(cursorName))) + + return HCURSOR(ret) +} + +func LoadCursorWithResourceID(instance HINSTANCE, res uint16) HCURSOR { + ret, _, _ := procLoadCursor.Call( + uintptr(instance), + uintptr(res)) + + return HCURSOR(ret) +} + +func MessageBoxIndirect(msgbox *MSGBOXPARAMS) int32 { + ret, _, _ := procMessageBoxIndirect.Call( + uintptr(unsafe.Pointer(msgbox))) + + return int32(ret) +} + +func ShowWindow(hwnd HWND, cmdshow int) bool { + ret, _, _ := procShowWindow.Call( + uintptr(hwnd), + uintptr(cmdshow)) + + return ret != 0 +} + +func IsZoomed(hwnd HWND) bool { + ret, _, _ := procIsZoomed.Call(uintptr(hwnd)) + return ret != 0 +} + +func ShowWindowAsync(hwnd HWND, cmdshow int) bool { + ret, _, _ := procShowWindowAsync.Call( + uintptr(hwnd), + uintptr(cmdshow)) + + return ret != 0 +} + +func UpdateWindow(hwnd HWND) bool { + ret, _, _ := procUpdateWindow.Call( + uintptr(hwnd)) + return ret != 0 +} + +func UpdateLayeredWindow(hwnd HWND, hdcDst HDC, pptDst *POINT, psize *SIZE, + hdcSrc HDC, pptSrc *POINT, crKey COLORREF, pblend *BLENDFUNCTION, dwFlags DWORD) bool { + ret, _, _ := procUpdateLayeredWindow.Call( + hwnd, + hdcDst, + uintptr(unsafe.Pointer(pptDst)), + uintptr(unsafe.Pointer(psize)), + hdcSrc, + uintptr(unsafe.Pointer(pptSrc)), + uintptr(crKey), + uintptr(unsafe.Pointer(pblend)), + uintptr(dwFlags)) + return ret != 0 +} + +func PostThreadMessage(threadID HANDLE, msg int, wp, lp uintptr) { + procPostThreadMessageW.Call(threadID, uintptr(msg), wp, lp) +} + +func RegisterWindowMessage(name *uint16) uint32 { + ret, _, _ := procRegisterWindowMessageW.Call(uintptr(unsafe.Pointer(name))) + return uint32(ret) +} + +func PostMainThreadMessage(msg uint32, wp, lp uintptr) bool { + ret, _, _ := procPostThreadMessageW.Call(mainThread, uintptr(msg), wp, lp) + return ret != 0 +} + +func CreateWindowEx(exStyle uint, className, windowName *uint16, + style uint, x, y, width, height int, parent HWND, menu HMENU, + instance HINSTANCE, param unsafe.Pointer) HWND { + ret, _, _ := procCreateWindowEx.Call( + uintptr(exStyle), + uintptr(unsafe.Pointer(className)), + uintptr(unsafe.Pointer(windowName)), + uintptr(style), + uintptr(x), + uintptr(y), + uintptr(width), + uintptr(height), + uintptr(parent), + uintptr(menu), + uintptr(instance), + uintptr(param)) + + return HWND(ret) +} + +func AdjustWindowRectEx(rect *RECT, style uint, menu bool, exStyle uint) bool { + ret, _, _ := procAdjustWindowRectEx.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(style), + uintptr(BoolToBOOL(menu)), + uintptr(exStyle)) + + return ret != 0 +} + +func AdjustWindowRect(rect *RECT, style uint, menu bool) bool { + ret, _, _ := procAdjustWindowRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(style), + uintptr(BoolToBOOL(menu))) + + return ret != 0 +} + +func DestroyWindow(hwnd HWND) bool { + ret, _, _ := procDestroyWindow.Call(hwnd) + return ret != 0 +} + +func HasGetDpiForWindowFunc() bool { + err := procGetDpiForWindow.Find() + return err == nil +} + +func GetDpiForWindow(hwnd HWND) UINT { + dpi, _, _ := procGetDpiForWindow.Call(hwnd) + return uint(dpi) +} + +func HasSetProcessDPIAwareFunc() bool { + err := procSetProcessDPIAware.Find() + return err == nil +} + +func GetClassName(hwnd HWND) string { + var buf [256]uint16 + procGetClassName.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(len(buf))) + + return syscall.UTF16ToString(buf[:]) +} + +func SetProcessDPIAware() error { + status, r, err := procSetProcessDPIAware.Call() + if status == 0 { + return fmt.Errorf("SetProcessDPIAware failed %d: %v %v", status, r, err) + } + return nil +} + +func HasSetProcessDpiAwarenessContextFunc() bool { + err := procSetProcessDpiAwarenessContext.Find() + return err == nil +} + +func SetProcessDpiAwarenessContext(ctx uintptr) error { + status, r, err := procSetProcessDpiAwarenessContext.Call(ctx) + if status == 0 { + return fmt.Errorf("SetProcessDpiAwarenessContext failed %d: %v %v", status, r, err) + } + return nil +} + +func GetForegroundWindow() HWND { + ret, _, _ := procGetForegroundWindow.Call() + return HWND(ret) +} + +func SetWindowCompositionAttribute(hwnd HWND, data *WINDOWCOMPOSITIONATTRIBDATA) bool { + if procSetWindowCompositionAttribute != nil { + ret, _, _ := procSetWindowCompositionAttribute.Call( + hwnd, + uintptr(unsafe.Pointer(data)), + ) + return ret != 0 + } + return false +} + +func DefWindowProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procDefWindowProc.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func DefDlgProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procDefDlgProc.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func PostQuitMessage(exitCode int) { + procPostQuitMessage.Call( + uintptr(exitCode)) +} + +func GetMessage(msg *MSG, hwnd HWND, msgFilterMin, msgFilterMax uint32) int { + ret, _, _ := procGetMessage.Call( + uintptr(unsafe.Pointer(msg)), + uintptr(hwnd), + uintptr(msgFilterMin), + uintptr(msgFilterMax)) + + return int(ret) +} + +func TranslateMessage(msg *MSG) bool { + ret, _, _ := procTranslateMessage.Call( + uintptr(unsafe.Pointer(msg))) + + return ret != 0 + +} + +func DispatchMessage(msg *MSG) uintptr { + ret, _, _ := procDispatchMessage.Call( + uintptr(unsafe.Pointer(msg))) + + return ret + +} + +func SendMessage(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procSendMessage.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func PostMessage(hwnd HWND, msg uint32, wParam, lParam uintptr) bool { + ret, _, _ := procPostMessage.Call( + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret != 0 +} + +func WaitMessage() bool { + ret, _, _ := procWaitMessage.Call() + return ret != 0 +} + +func SetWindowText(hwnd HWND, text string) { + procSetWindowText.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) +} + +func GetWindowTextLength(hwnd HWND) int { + ret, _, _ := procGetWindowTextLength.Call( + uintptr(hwnd)) + + return int(ret) +} + +func GetWindowInfo(hwnd HWND, info *WINDOWINFO) int { + ret, _, _ := procGetWindowInfo.Call( + hwnd, + uintptr(unsafe.Pointer(info)), + ) + return int(ret) +} + +func GetWindow(hwnd HWND, cmd uint32) HWND { + ret, _, _ := procGetWindow.Call( + hwnd, + uintptr(cmd), + ) + return HWND(ret) +} + +func GetWindowText(hwnd HWND) string { + textLen := GetWindowTextLength(hwnd) + 1 + + buf := make([]uint16, textLen) + procGetWindowText.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(textLen)) + + return syscall.UTF16ToString(buf) +} + +func GetWindowRect(hwnd HWND) *RECT { + var rect RECT + procGetWindowRect.Call( + hwnd, + uintptr(unsafe.Pointer(&rect))) + + return &rect +} + +func MoveWindow(hwnd HWND, x, y, width, height int, repaint bool) bool { + ret, _, _ := procMoveWindow.Call( + uintptr(hwnd), + uintptr(x), + uintptr(y), + uintptr(width), + uintptr(height), + uintptr(BoolToBOOL(repaint))) + + return ret != 0 + +} + +func ScreenToClient(hwnd HWND, x, y int) (X, Y int, ok bool) { + pt := POINT{X: int32(x), Y: int32(y)} + ret, _, _ := procScreenToClient.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y), ret != 0 +} + +func CallWindowProc(preWndProc uintptr, hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr { + ret, _, _ := procCallWindowProc.Call( + preWndProc, + uintptr(hwnd), + uintptr(msg), + wParam, + lParam) + + return ret +} + +func SetWindowLong(hwnd HWND, index int, value uint32) uint32 { + ret, _, _ := procSetWindowLong.Call( + uintptr(hwnd), + uintptr(index), + uintptr(value)) + + return uint32(ret) +} + +func SetWindowLongPtr(hwnd HWND, index int, value uintptr) uintptr { + ret, _, _ := procSetWindowLongPtr.Call( + uintptr(hwnd), + uintptr(index), + value) + + return ret +} + +func GetWindowLong(hwnd HWND, index int) int32 { + ret, _, _ := procGetWindowLong.Call( + uintptr(hwnd), + uintptr(index)) + + return int32(ret) +} + +func GetWindowLongPtr(hwnd HWND, index int) uintptr { + ret, _, _ := procGetWindowLongPtr.Call( + uintptr(hwnd), + uintptr(index)) + + return ret +} + +func EnableWindow(hwnd HWND, b bool) bool { + ret, _, _ := procEnableWindow.Call( + uintptr(hwnd), + uintptr(BoolToBOOL(b))) + return ret != 0 +} + +func IsWindowEnabled(hwnd HWND) bool { + ret, _, _ := procIsWindowEnabled.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func IsWindowVisible(hwnd HWND) bool { + ret, _, _ := procIsWindowVisible.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func SetFocus(hwnd HWND) HWND { + ret, _, _ := procSetFocus.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func SetActiveWindow(hwnd HWND) HWND { + ret, _, _ := procSetActiveWindow.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func BringWindowToTop(hwnd HWND) bool { + ret, _, _ := procBringWindowToTop.Call(uintptr(hwnd)) + return ret != 0 +} + +func SetForegroundWindow(hwnd HWND) HWND { + ret, _, _ := procSetForegroundWindow.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func GetFocus() HWND { + ret, _, _ := procGetFocus.Call() + return HWND(ret) +} + +func InvalidateRect(hwnd HWND, rect *RECT, erase bool) bool { + ret, _, _ := procInvalidateRect.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(rect)), + uintptr(BoolToBOOL(erase))) + + return ret != 0 +} + +func GetClientRect(hwnd HWND) *RECT { + var rect RECT + ret, _, _ := procGetClientRect.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&rect))) + + if ret == 0 { + panic(fmt.Sprintf("GetClientRect(%d) failed", hwnd)) + } + + return &rect +} + +func GetDC(hwnd HWND) HDC { + ret, _, _ := procGetDC.Call( + uintptr(hwnd)) + + return HDC(ret) +} + +func ReleaseDC(hwnd HWND, hDC HDC) bool { + ret, _, _ := procReleaseDC.Call( + uintptr(hwnd), + uintptr(hDC)) + + return ret != 0 +} + +func SetCapture(hwnd HWND) HWND { + ret, _, _ := procSetCapture.Call( + uintptr(hwnd)) + + return HWND(ret) +} + +func ReleaseCapture() bool { + ret, _, _ := procReleaseCapture.Call() + + return ret != 0 +} + +func EnumWindows(enumFunc uintptr, lparam uintptr) bool { + ret, _, _ := procEnumWindows.Call( + enumFunc, + lparam) + + return ret != 0 +} + +func GetWindowThreadProcessId(hwnd HWND) (HANDLE, int) { + var processId int + ret, _, _ := procGetWindowThreadProcessId.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&processId))) + + return HANDLE(ret), processId +} + +func MessageBox(hwnd HWND, title, caption string, flags uint) int { + ret, _, _ := procMessageBox.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), + uintptr(flags)) + + return int(ret) +} + +func GetSystemMetrics(index int) int { + ret, _, _ := procGetSystemMetrics.Call( + uintptr(index)) + + return int(ret) +} + +func GetSysColorBrush(nIndex int) HBRUSH { + ret, _, _ := procGetSysColorBrush.Call(1, + uintptr(nIndex), + 0, + 0) + + return HBRUSH(ret) +} + +func GetSysColor(nIndex int) COLORREF { + ret, _, _ := procGetSysColor.Call( + uintptr(nIndex)) + + return COLORREF(ret) +} + +func CopyRect(dst, src *RECT) bool { + ret, _, _ := procCopyRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src))) + + return ret != 0 +} + +func EqualRect(rect1, rect2 *RECT) bool { + ret, _, _ := procEqualRect.Call( + uintptr(unsafe.Pointer(rect1)), + uintptr(unsafe.Pointer(rect2))) + + return ret != 0 +} + +func InflateRect(rect *RECT, dx, dy int) bool { + ret, _, _ := procInflateRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(dx), + uintptr(dy)) + + return ret != 0 +} + +func IntersectRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procIntersectRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func IsRectEmpty(rect *RECT) bool { + ret, _, _ := procIsRectEmpty.Call( + uintptr(unsafe.Pointer(rect))) + + return ret != 0 +} + +func OffsetRect(rect *RECT, dx, dy int) bool { + ret, _, _ := procOffsetRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(dx), + uintptr(dy)) + + return ret != 0 +} + +func PtInRect(rect *RECT, x, y int) bool { + pt := POINT{X: int32(x), Y: int32(y)} + ret, _, _ := procPtInRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(unsafe.Pointer(&pt))) + + return ret != 0 +} + +func RectInRect(rect1, rect2 *RECT) bool { + return rect1.Left >= rect2.Left && rect1.Right <= rect2.Right && + rect1.Top >= rect2.Top && rect1.Bottom <= rect2.Bottom +} + +func SetRect(rect *RECT, left, top, right, bottom int) bool { + ret, _, _ := procSetRect.Call( + uintptr(unsafe.Pointer(rect)), + uintptr(left), + uintptr(top), + uintptr(right), + uintptr(bottom)) + + return ret != 0 +} + +func SetRectEmpty(rect *RECT) bool { + ret, _, _ := procSetRectEmpty.Call( + uintptr(unsafe.Pointer(rect))) + + return ret != 0 +} + +func SubtractRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procSubtractRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func UnionRect(dst, src1, src2 *RECT) bool { + ret, _, _ := procUnionRect.Call( + uintptr(unsafe.Pointer(dst)), + uintptr(unsafe.Pointer(src1)), + uintptr(unsafe.Pointer(src2))) + + return ret != 0 +} + +func CreateDialog(hInstance HINSTANCE, lpTemplate *uint16, hWndParent HWND, lpDialogProc uintptr) HWND { + ret, _, _ := procCreateDialogParam.Call( + uintptr(hInstance), + uintptr(unsafe.Pointer(lpTemplate)), + uintptr(hWndParent), + lpDialogProc, + 0) + + return HWND(ret) +} + +func DialogBox(hInstance HINSTANCE, lpTemplateName *uint16, hWndParent HWND, lpDialogProc uintptr) int { + ret, _, _ := procDialogBoxParam.Call( + uintptr(hInstance), + uintptr(unsafe.Pointer(lpTemplateName)), + uintptr(hWndParent), + lpDialogProc, + 0) + + return int(ret) +} + +func GetDlgItem(hDlg HWND, nIDDlgItem int) HWND { + ret, _, _ := procGetDlgItem.Call( + uintptr(unsafe.Pointer(hDlg)), + uintptr(nIDDlgItem)) + + return HWND(ret) +} + +func DrawIcon(hDC HDC, x, y int, hIcon HICON) bool { + ret, _, _ := procDrawIcon.Call( + uintptr(unsafe.Pointer(hDC)), + uintptr(x), + uintptr(y), + uintptr(unsafe.Pointer(hIcon))) + + return ret != 0 +} + +func CreateMenu() HMENU { + ret, _, _ := procCreateMenu.Call(0, + 0, + 0, + 0) + + return HMENU(ret) +} + +func SetMenu(hWnd HWND, hMenu HMENU) bool { + + ret, _, _ := procSetMenu.Call(hWnd, hMenu) + return ret != 0 +} + +func AppendMenu(hMenu HMENU, uFlags uint32, uIDNewItem uintptr, lpNewItem *uint16) bool { + ret, _, _ := procAppendMenu.Call( + hMenu, + uintptr(uFlags), + uIDNewItem, + uintptr(unsafe.Pointer(lpNewItem))) + + return ret != 0 +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-checkmenuradioitem +func SelectRadioMenuItem(menuID uint16, startID uint16, endID uint16, hwnd HWND) bool { + ret, _, _ := procCheckMenuRadioItem.Call( + hwnd, + uintptr(startID), + uintptr(endID), + uintptr(menuID), + MF_BYCOMMAND) + return ret != 0 + +} + +func CreatePopupMenu() PopupMenu { + ret, _, _ := procCreatePopupMenu.Call(0, + 0, + 0, + 0) + + return PopupMenu(ret) +} + +func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm *TPMPARAMS) bool { + + ret, _, _ := procTrackPopupMenuEx.Call( + hMenu, + uintptr(fuFlags), + uintptr(x), + uintptr(y), + hWnd, + uintptr(unsafe.Pointer(lptpm))) + + return ret != 0 +} + +func DrawMenuBar(hWnd HWND) bool { + ret, _, _ := procDrawMenuBar.Call(hWnd, 0, 0) + return ret != 0 +} + +func InsertMenuItem(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procInsertMenuItem.Call( + hMenu, + uintptr(uItem), + uintptr(BoolToBOOL(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + 0, + 0) + + return ret != 0 +} + +func SetMenuItemInfo(hMenu HMENU, uItem uint32, fByPosition bool, lpmii *MENUITEMINFO) bool { + ret, _, _ := procSetMenuItemInfo.Call( + hMenu, + uintptr(uItem), + uintptr(BoolToBOOL(fByPosition)), + uintptr(unsafe.Pointer(lpmii)), + 0, + 0) + + return ret != 0 +} + +func ClientToScreen(hwnd HWND, x, y int) (int, int) { + pt := POINT{X: int32(x), Y: int32(y)} + + procClientToScreen.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(&pt))) + + return int(pt.X), int(pt.Y) +} + +func IsDialogMessage(hwnd HWND, msg *MSG) bool { + ret, _, _ := procIsDialogMessage.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(msg))) + + return ret != 0 +} + +func IsWindow(hwnd HWND) bool { + ret, _, _ := procIsWindow.Call( + uintptr(hwnd)) + + return ret != 0 +} + +func EndDialog(hwnd HWND, nResult uintptr) bool { + ret, _, _ := procEndDialog.Call( + uintptr(hwnd), + nResult) + + return ret != 0 +} + +func PeekMessage(lpMsg *MSG, hwnd HWND, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool { + ret, _, _ := procPeekMessage.Call( + uintptr(unsafe.Pointer(lpMsg)), + uintptr(hwnd), + uintptr(wMsgFilterMin), + uintptr(wMsgFilterMax), + uintptr(wRemoveMsg)) + + return ret != 0 +} + +func TranslateAccelerator(hwnd HWND, hAccTable HACCEL, lpMsg *MSG) bool { + ret, _, _ := procTranslateMessage.Call( + uintptr(hwnd), + uintptr(hAccTable), + uintptr(unsafe.Pointer(lpMsg))) + + return ret != 0 +} + +func SetWindowPos(hwnd, hWndInsertAfter HWND, x, y, cx, cy int, uFlags uint) bool { + ret, _, _ := procSetWindowPos.Call( + uintptr(hwnd), + uintptr(hWndInsertAfter), + uintptr(x), + uintptr(y), + uintptr(cx), + uintptr(cy), + uintptr(uFlags)) + + return ret != 0 +} + +func FillRect(hDC HDC, lprc *RECT, hbr HBRUSH) bool { + ret, _, _ := procFillRect.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(lprc)), + uintptr(hbr)) + + return ret != 0 +} + +func DrawText(hDC HDC, text []uint16, uCount int, lpRect *RECT, uFormat uint32) int { + + // Convert the string to a UTF16 pointer + // This is necessary because the DrawText function expects a UTF16 pointer + + ret, _, _ := procDrawText.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(&text[0])), + uintptr(uCount), + uintptr(unsafe.Pointer(lpRect)), + uintptr(uFormat)) + + return int(ret) +} + +func AddClipboardFormatListener(hwnd HWND) bool { + ret, _, _ := procAddClipboardFormatListener.Call( + uintptr(hwnd)) + return ret != 0 +} + +func RemoveClipboardFormatListener(hwnd HWND) bool { + ret, _, _ := procRemoveClipboardFormatListener.Call( + uintptr(hwnd)) + return ret != 0 +} + +func OpenClipboard(hWndNewOwner HWND) bool { + ret, _, _ := procOpenClipboard.Call( + uintptr(hWndNewOwner)) + return ret != 0 +} + +func CloseClipboard() bool { + ret, _, _ := procCloseClipboard.Call() + return ret != 0 +} + +func EnumClipboardFormats(format uint) uint { + ret, _, _ := procEnumClipboardFormats.Call( + uintptr(format)) + return uint(ret) +} + +func GetClipboardData(uFormat uint) HANDLE { + ret, _, _ := procGetClipboardData.Call( + uintptr(uFormat)) + return HANDLE(ret) +} + +func SetClipboardData(uFormat uint, hMem HANDLE) HANDLE { + ret, _, _ := procSetClipboardData.Call( + uintptr(uFormat), + uintptr(hMem)) + return HANDLE(ret) +} + +func EmptyClipboard() bool { + ret, _, _ := procEmptyClipboard.Call() + return ret != 0 +} + +func GetClipboardFormatName(format uint) (string, bool) { + cchMaxCount := 255 + buf := make([]uint16, cchMaxCount) + ret, _, _ := procGetClipboardFormatName.Call( + uintptr(format), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(cchMaxCount)) + + if ret > 0 { + return syscall.UTF16ToString(buf), true + } + + return "Requested format does not exist or is predefined", false +} + +func IsClipboardFormatAvailable(format uint) bool { + ret, _, _ := procIsClipboardFormatAvailable.Call(uintptr(format)) + return ret != 0 +} + +func BeginPaint(hwnd HWND, paint *PAINTSTRUCT) HDC { + ret, _, _ := procBeginPaint.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(paint))) + return HDC(ret) +} + +func EndPaint(hwnd HWND, paint *PAINTSTRUCT) { + procEndPaint.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(paint))) +} + +func GetKeyboardState(keyState []byte) bool { + if len(keyState) != 256 { + panic("keyState slice must have a size of 256 bytes") + } + ret, _, _ := procGetKeyboardState.Call(uintptr(unsafe.Pointer(&keyState[0]))) + return ret != 0 +} + +func MapVirtualKeyEx(uCode, uMapType uint, dwhkl HKL) uint { + ret, _, _ := procMapVirtualKeyEx.Call( + uintptr(uCode), + uintptr(uMapType), + uintptr(dwhkl)) + return uint(ret) +} + +func MapVirtualKey(uCode uint, uMapType uint) uint { + ret, _, _ := procMapVirtualKey.Call(uintptr(uCode), uintptr(uMapType)) + return uint(ret) +} + +func GetAsyncKeyState(vKey int) uint16 { + ret, _, _ := procGetAsyncKeyState.Call(uintptr(vKey)) + return uint16(ret) +} + +func ToAscii(uVirtKey, uScanCode uint, lpKeyState *byte, lpChar *uint16, uFlags uint) int { + ret, _, _ := procToAscii.Call( + uintptr(uVirtKey), + uintptr(uScanCode), + uintptr(unsafe.Pointer(lpKeyState)), + uintptr(unsafe.Pointer(lpChar)), + uintptr(uFlags)) + return int(ret) +} + +func SwapMouseButton(fSwap bool) bool { + ret, _, _ := procSwapMouseButton.Call( + uintptr(BoolToBOOL(fSwap))) + return ret != 0 +} + +func GetCursorPos() (x, y int, ok bool) { + pt := POINT{} + ret, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt))) + return int(pt.X), int(pt.Y), ret != 0 +} + +func SetCursorPos(x, y int) bool { + ret, _, _ := procSetCursorPos.Call( + uintptr(x), + uintptr(y), + ) + return ret != 0 +} + +func SetCursor(cursor HCURSOR) HCURSOR { + ret, _, _ := procSetCursor.Call( + uintptr(cursor), + ) + return HCURSOR(ret) +} + +func CreateIcon(instance HINSTANCE, nWidth, nHeight int, cPlanes, cBitsPerPixel byte, ANDbits, XORbits *byte) HICON { + ret, _, _ := procCreateIcon.Call( + uintptr(instance), + uintptr(nWidth), + uintptr(nHeight), + uintptr(cPlanes), + uintptr(cBitsPerPixel), + uintptr(unsafe.Pointer(ANDbits)), + uintptr(unsafe.Pointer(XORbits)), + ) + return HICON(ret) +} + +func DestroyIcon(icon HICON) bool { + ret, _, _ := procDestroyIcon.Call( + uintptr(icon), + ) + return ret != 0 +} + +func MonitorFromPoint(x, y int, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromPoint.Call( + uintptr(x), + uintptr(y), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func MonitorFromRect(rc *RECT, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromRect.Call( + uintptr(unsafe.Pointer(rc)), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func MonitorFromWindow(hwnd HWND, dwFlags uint32) HMONITOR { + ret, _, _ := procMonitorFromWindow.Call( + uintptr(hwnd), + uintptr(dwFlags), + ) + return HMONITOR(ret) +} + +func GetMonitorInfo(hMonitor HMONITOR, lmpi *MONITORINFO) bool { + ret, _, _ := procGetMonitorInfo.Call( + uintptr(hMonitor), + uintptr(unsafe.Pointer(lmpi)), + ) + return ret != 0 +} + +func GetMonitorInfoEx(hMonitor HMONITOR, lmpi *MONITORINFOEX) bool { + ret, _, _ := procGetMonitorInfo.Call( + uintptr(hMonitor), + uintptr(unsafe.Pointer(lmpi)), + ) + return ret != 0 +} + +func EnumDisplayMonitors(hdc HDC, clip *RECT, fnEnum uintptr, dwData unsafe.Pointer) bool { + ret, _, _ := procEnumDisplayMonitors.Call( + hdc, + uintptr(unsafe.Pointer(clip)), + fnEnum, + uintptr(dwData), + ) + return ret != 0 +} + +func EnumDisplaySettingsEx(szDeviceName *uint16, iModeNum uint32, devMode *DEVMODE, dwFlags uint32) bool { + ret, _, _ := procEnumDisplaySettingsEx.Call( + uintptr(unsafe.Pointer(szDeviceName)), + uintptr(iModeNum), + uintptr(unsafe.Pointer(devMode)), + uintptr(dwFlags), + ) + return ret != 0 +} + +func ChangeDisplaySettingsEx(szDeviceName *uint16, devMode *DEVMODE, hwnd HWND, dwFlags uint32, lParam uintptr) int32 { + ret, _, _ := procChangeDisplaySettingsEx.Call( + uintptr(unsafe.Pointer(szDeviceName)), + uintptr(unsafe.Pointer(devMode)), + uintptr(hwnd), + uintptr(dwFlags), + lParam, + ) + return int32(ret) +} + +/* +func SendInput(inputs []INPUT) uint32 { + var validInputs []C.INPUT + + for _, oneInput := range inputs { + input := C.INPUT{_type: C.DWORD(oneInput.Type)} + + switch oneInput.Type { + case INPUT_MOUSE: + (*MouseInput)(unsafe.Pointer(&input)).mi = oneInput.Mi + case INPUT_KEYBOARD: + (*KbdInput)(unsafe.Pointer(&input)).ki = oneInput.Ki + case INPUT_HARDWARE: + (*HardwareInput)(unsafe.Pointer(&input)).hi = oneInput.Hi + default: + panic("unkown type") + } + + validInputs = append(validInputs, input) + } + + ret, _, _ := procSendInput.Call( + uintptr(len(validInputs)), + uintptr(unsafe.Pointer(&validInputs[0])), + uintptr(unsafe.Sizeof(C.INPUT{})), + ) + return uint32(ret) +}*/ + +func SetWindowsHookEx(idHook int, lpfn HOOKPROC, hMod HINSTANCE, dwThreadId DWORD) HHOOK { + ret, _, _ := procSetWindowsHookEx.Call( + uintptr(idHook), + uintptr(syscall.NewCallback(lpfn)), + uintptr(hMod), + uintptr(dwThreadId), + ) + return HHOOK(ret) +} + +func UnhookWindowsHookEx(hhk HHOOK) bool { + ret, _, _ := procUnhookWindowsHookEx.Call( + uintptr(hhk), + ) + return ret != 0 +} + +func CallNextHookEx(hhk HHOOK, nCode int, wParam WPARAM, lParam LPARAM) LRESULT { + ret, _, _ := procCallNextHookEx.Call( + uintptr(hhk), + uintptr(nCode), + uintptr(wParam), + uintptr(lParam), + ) + return LRESULT(ret) +} + +func GetKeyState(nVirtKey int32) int16 { + ret, _, _ := procGetKeyState.Call( + uintptr(nVirtKey), + 0, + 0) + + return int16(ret) +} + +func DestroyMenu(hMenu HMENU) bool { + ret, _, _ := procDestroyMenu.Call(1, + uintptr(hMenu), + 0, + 0) + + return ret != 0 +} + +func GetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { + ret, _, _ := procGetWindowPlacement.Call( + uintptr(hWnd), + uintptr(unsafe.Pointer(lpwndpl)), + 0) + + return ret != 0 +} + +func SetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { + ret, _, _ := procSetWindowPlacement.Call( + uintptr(hWnd), + uintptr(unsafe.Pointer(lpwndpl)), + 0) + + return ret != 0 +} + +func SetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO, fRedraw bool) int32 { + ret, _, _ := procSetScrollInfo.Call( + hwnd, + uintptr(fnBar), + uintptr(unsafe.Pointer(lpsi)), + uintptr(BoolToBOOL(fRedraw)), + 0, + 0) + + return int32(ret) +} + +func GetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO) bool { + ret, _, _ := procGetScrollInfo.Call( + hwnd, + uintptr(fnBar), + uintptr(unsafe.Pointer(lpsi))) + + return ret != 0 +} + +func RedrawWindow(hwnd HWND, lprcUpdate *RECT, hrgnUpdate HRGN, flags uint32) bool { + ret, _, _ := procRedrawWindow.Call( + uintptr(hwnd), + uintptr(unsafe.Pointer(lprcUpdate)), + uintptr(hrgnUpdate), + uintptr(flags)) + return ret != 0 +} + +func FrameRect(hDC HDC, lprc *RECT, hbr HBRUSH) int { + ret, _, _ := procFrameRect.Call( + uintptr(hDC), + uintptr(unsafe.Pointer(lprc)), + uintptr(hbr)) + return int(ret) +} + +func GetMenuBarInfo(hwnd HWND, idObject int32, idItem uint32, pmbi *MENUBARINFO) bool { + ret, _, _ := procGetMenuBarInfo.Call( + uintptr(hwnd), + uintptr(idObject), + uintptr(idItem), + uintptr(unsafe.Pointer(pmbi))) + return ret != 0 +} + +func MapWindowPoints(hwndFrom, hwndTo HWND, lpPoints *POINT, cPoints uint) int { + ret, _, _ := procMapWindowPoints.Call( + uintptr(hwndFrom), + uintptr(hwndTo), + uintptr(unsafe.Pointer(lpPoints)), + uintptr(cPoints)) + return int(ret) +} + +func TrackPopupMenu(hmenu HMENU, flags uint32, x, y int32, reserved int32, hwnd HWND, prcRect *RECT) bool { + ret, _, _ := procTrackPopupMenu.Call( + uintptr(hmenu), + uintptr(flags), + uintptr(x), + uintptr(y), + uintptr(reserved), + uintptr(hwnd), + uintptr(unsafe.Pointer(prcRect))) + return ret != 0 +} diff --git a/v3/pkg/w32/utils.go b/v3/pkg/w32/utils.go new file mode 100644 index 000000000..f4e99dbaf --- /dev/null +++ b/v3/pkg/w32/utils.go @@ -0,0 +1,637 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +import ( + "fmt" + "syscall" + "unicode/utf16" + "unsafe" +) + +func MustLoadLibrary(name string) uintptr { + lib, err := syscall.LoadLibrary(name) + if err != nil { + panic(err) + } + + return uintptr(lib) +} + +func MustGetProcAddress(lib uintptr, name string) uintptr { + addr, err := syscall.GetProcAddress(syscall.Handle(lib), name) + if err != nil { + panic(err) + } + + return uintptr(addr) +} + +func SUCCEEDED(hr HRESULT) bool { + return hr >= 0 +} + +func FAILED(hr HRESULT) bool { + return hr < 0 +} + +func LOWORD(dw uint32) uint16 { + return uint16(dw) +} + +func HIWORD(dw uint32) uint16 { + return uint16(dw >> 16 & 0xffff) +} + +func MAKELONG(lo, hi uint16) uint32 { + return uint32(uint32(lo) | ((uint32(hi)) << 16)) +} + +func BoolToBOOL(value bool) BOOL { + if value { + return 1 + } + + return 0 +} + +func UTF16PtrToString(cstr *uint16) string { + if cstr != nil { + us := make([]uint16, 0, 256) + for p := uintptr(unsafe.Pointer(cstr)); ; p += 2 { + u := *(*uint16)(unsafe.Pointer(p)) + if u == 0 { + return string(utf16.Decode(us)) + } + us = append(us, u) + } + } + + return "" +} + +func ComAddRef(unknown *IUnknown) int32 { + ret, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.AddRef), + uintptr(unsafe.Pointer(unknown)), + 0, + 0) + return int32(ret) +} + +func ComRelease(unknown *IUnknown) int32 { + ret, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.Release), + uintptr(unsafe.Pointer(unknown)), + 0, + 0) + return int32(ret) +} + +func ComQueryInterface(unknown *IUnknown, id *GUID) *IDispatch { + var disp *IDispatch + hr, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.QueryInterface), + uintptr(unsafe.Pointer(unknown)), + uintptr(unsafe.Pointer(id)), + uintptr(unsafe.Pointer(&disp))) + if hr != 0 { + panic("Invoke QieryInterface error.") + } + return disp +} + +func ComGetIDsOfName(disp *IDispatch, names []string) []int32 { + wnames := make([]*uint16, len(names)) + dispid := make([]int32, len(names)) + for i := 0; i < len(names); i++ { + wnames[i] = syscall.StringToUTF16Ptr(names[i]) + } + hr, _, _ := syscall.SyscallN(disp.lpVtbl.pGetIDsOfNames, + uintptr(unsafe.Pointer(disp)), + uintptr(unsafe.Pointer(IID_NULL)), + uintptr(unsafe.Pointer(&wnames[0])), + uintptr(len(names)), + uintptr(GetUserDefaultLCID()), + uintptr(unsafe.Pointer(&dispid[0]))) + if hr != 0 { + panic("Invoke GetIDsOfName error.") + } + return dispid +} + +func ComInvoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (result *VARIANT) { + var dispparams DISPPARAMS + + if dispatch&DISPATCH_PROPERTYPUT != 0 { + dispnames := [1]int32{DISPID_PROPERTYPUT} + dispparams.RgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0])) + dispparams.CNamedArgs = 1 + } + var vargs []VARIANT + if len(params) > 0 { + vargs = make([]VARIANT, len(params)) + for i, v := range params { + //n := len(params)-i-1 + n := len(params) - i - 1 + VariantInit(&vargs[n]) + switch v.(type) { + case bool: + if v.(bool) { + vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0xffff} + } else { + vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0} + } + case *bool: + vargs[n] = VARIANT{VT_BOOL | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*bool))))} + case byte: + vargs[n] = VARIANT{VT_I1, 0, 0, 0, int64(v.(byte))} + case *byte: + vargs[n] = VARIANT{VT_I1 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*byte))))} + case int16: + vargs[n] = VARIANT{VT_I2, 0, 0, 0, int64(v.(int16))} + case *int16: + vargs[n] = VARIANT{VT_I2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int16))))} + case uint16: + vargs[n] = VARIANT{VT_UI2, 0, 0, 0, int64(v.(int16))} + case *uint16: + vargs[n] = VARIANT{VT_UI2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint16))))} + case int, int32: + vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(int))} + case *int, *int32: + vargs[n] = VARIANT{VT_I4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int))))} + case uint, uint32: + vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(uint))} + case *uint, *uint32: + vargs[n] = VARIANT{VT_UI4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint))))} + case int64: + vargs[n] = VARIANT{VT_I8, 0, 0, 0, v.(int64)} + case *int64: + vargs[n] = VARIANT{VT_I8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int64))))} + case uint64: + vargs[n] = VARIANT{VT_UI8, 0, 0, 0, int64(v.(uint64))} + case *uint64: + vargs[n] = VARIANT{VT_UI8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint64))))} + case float32: + vargs[n] = VARIANT{VT_R4, 0, 0, 0, int64(v.(float32))} + case *float32: + vargs[n] = VARIANT{VT_R4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float32))))} + case float64: + vargs[n] = VARIANT{VT_R8, 0, 0, 0, int64(v.(float64))} + case *float64: + vargs[n] = VARIANT{VT_R8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float64))))} + case string: + vargs[n] = VARIANT{VT_BSTR, 0, 0, 0, int64(uintptr(unsafe.Pointer(SysAllocString(v.(string)))))} + case *string: + vargs[n] = VARIANT{VT_BSTR | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*string))))} + case *IDispatch: + vargs[n] = VARIANT{VT_DISPATCH, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*IDispatch))))} + case **IDispatch: + vargs[n] = VARIANT{VT_DISPATCH | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(**IDispatch))))} + case nil: + vargs[n] = VARIANT{VT_NULL, 0, 0, 0, 0} + case *VARIANT: + vargs[n] = VARIANT{VT_VARIANT | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*VARIANT))))} + default: + panic("unknown type") + } + } + dispparams.Rgvarg = uintptr(unsafe.Pointer(&vargs[0])) + dispparams.CArgs = uint32(len(params)) + } + + var ret VARIANT + var excepInfo EXCEPINFO + VariantInit(&ret) + hr, _, _ := syscall.SyscallN(disp.lpVtbl.pInvoke, + uintptr(unsafe.Pointer(disp)), + uintptr(dispid), + uintptr(unsafe.Pointer(IID_NULL)), + uintptr(GetUserDefaultLCID()), + uintptr(dispatch), + uintptr(unsafe.Pointer(&dispparams)), + uintptr(unsafe.Pointer(&ret)), + uintptr(unsafe.Pointer(&excepInfo)), + 0) + if hr != 0 { + if excepInfo.BstrDescription != nil { + bs := UTF16PtrToString(excepInfo.BstrDescription) + panic(bs) + } + } + for _, varg := range vargs { + if varg.VT == VT_BSTR && varg.Val != 0 { + SysFreeString(((*int16)(unsafe.Pointer(uintptr(varg.Val))))) + } + } + result = &ret + return +} + +func WMMessageToString(msg uintptr) string { + // Convert windows message to string + switch msg { + case WM_APP: + return "WM_APP" + case WM_ACTIVATE: + return "WM_ACTIVATE" + case WM_ACTIVATEAPP: + return "WM_ACTIVATEAPP" + case WM_AFXFIRST: + return "WM_AFXFIRST" + case WM_AFXLAST: + return "WM_AFXLAST" + case WM_ASKCBFORMATNAME: + return "WM_ASKCBFORMATNAME" + case WM_CANCELJOURNAL: + return "WM_CANCELJOURNAL" + case WM_CANCELMODE: + return "WM_CANCELMODE" + case WM_CAPTURECHANGED: + return "WM_CAPTURECHANGED" + case WM_CHANGECBCHAIN: + return "WM_CHANGECBCHAIN" + case WM_CHAR: + return "WM_CHAR" + case WM_CHARTOITEM: + return "WM_CHARTOITEM" + case WM_CHILDACTIVATE: + return "WM_CHILDACTIVATE" + case WM_CLEAR: + return "WM_CLEAR" + case WM_CLOSE: + return "WM_CLOSE" + case WM_COMMAND: + return "WM_COMMAND" + case WM_COMMNOTIFY /* OBSOLETE */ : + return "WM_COMMNOTIFY" + case WM_COMPACTING: + return "WM_COMPACTING" + case WM_COMPAREITEM: + return "WM_COMPAREITEM" + case WM_CONTEXTMENU: + return "WM_CONTEXTMENU" + case WM_COPY: + return "WM_COPY" + case WM_COPYDATA: + return "WM_COPYDATA" + case WM_CREATE: + return "WM_CREATE" + case WM_CTLCOLORBTN: + return "WM_CTLCOLORBTN" + case WM_CTLCOLORDLG: + return "WM_CTLCOLORDLG" + case WM_CTLCOLOREDIT: + return "WM_CTLCOLOREDIT" + case WM_CTLCOLORLISTBOX: + return "WM_CTLCOLORLISTBOX" + case WM_CTLCOLORMSGBOX: + return "WM_CTLCOLORMSGBOX" + case WM_CTLCOLORSCROLLBAR: + return "WM_CTLCOLORSCROLLBAR" + case WM_CTLCOLORSTATIC: + return "WM_CTLCOLORSTATIC" + case WM_CUT: + return "WM_CUT" + case WM_DEADCHAR: + return "WM_DEADCHAR" + case WM_DELETEITEM: + return "WM_DELETEITEM" + case WM_DESTROY: + return "WM_DESTROY" + case WM_DESTROYCLIPBOARD: + return "WM_DESTROYCLIPBOARD" + case WM_DEVICECHANGE: + return "WM_DEVICECHANGE" + case WM_DEVMODECHANGE: + return "WM_DEVMODECHANGE" + case WM_DISPLAYCHANGE: + return "WM_DISPLAYCHANGE" + case WM_DRAWCLIPBOARD: + return "WM_DRAWCLIPBOARD" + case WM_DRAWITEM: + return "WM_DRAWITEM" + case WM_DROPFILES: + return "WM_DROPFILES" + case WM_ENABLE: + return "WM_ENABLE" + case WM_ENDSESSION: + return "WM_ENDSESSION" + case WM_ENTERIDLE: + return "WM_ENTERIDLE" + case WM_ENTERMENULOOP: + return "WM_ENTERMENULOOP" + case WM_ENTERSIZEMOVE: + return "WM_ENTERSIZEMOVE" + case WM_ERASEBKGND: + return "WM_ERASEBKGND" + case WM_EXITMENULOOP: + return "WM_EXITMENULOOP" + case WM_EXITSIZEMOVE: + return "WM_EXITSIZEMOVE" + case WM_FONTCHANGE: + return "WM_FONTCHANGE" + case WM_GETDLGCODE: + return "WM_GETDLGCODE" + case WM_GETFONT: + return "WM_GETFONT" + case WM_GETHOTKEY: + return "WM_GETHOTKEY" + case WM_GETICON: + return "WM_GETICON" + case WM_GETMINMAXINFO: + return "WM_GETMINMAXINFO" + case WM_GETTEXT: + return "WM_GETTEXT" + case WM_GETTEXTLENGTH: + return "WM_GETTEXTLENGTH" + case WM_HANDHELDFIRST: + return "WM_HANDHELDFIRST" + case WM_HANDHELDLAST: + return "WM_HANDHELDLAST" + case WM_HELP: + return "WM_HELP" + case WM_HOTKEY: + return "WM_HOTKEY" + case WM_HSCROLL: + return "WM_HSCROLL" + case WM_HSCROLLCLIPBOARD: + return "WM_HSCROLLCLIPBOARD" + case WM_ICONERASEBKGND: + return "WM_ICONERASEBKGND" + case WM_INITDIALOG: + return "WM_INITDIALOG" + case WM_INITMENU: + return "WM_INITMENU" + case WM_INITMENUPOPUP: + return "WM_INITMENUPOPUP" + case WM_INPUT: + return "WM_INPUT" + case WM_INPUTLANGCHANGE: + return "WM_INPUTLANGCHANGE" + case WM_INPUTLANGCHANGEREQUEST: + return "WM_INPUTLANGCHANGEREQUEST" + case WM_KEYDOWN: + return "WM_KEYDOWN" + case WM_KEYUP: + return "WM_KEYUP" + case WM_KILLFOCUS: + return "WM_KILLFOCUS" + case WM_MDIACTIVATE: + return "WM_MDIACTIVATE" + case WM_MDICASCADE: + return "WM_MDICASCADE" + case WM_MDICREATE: + return "WM_MDICREATE" + case WM_MDIDESTROY: + return "WM_MDIDESTROY" + case WM_MDIGETACTIVE: + return "WM_MDIGETACTIVE" + case WM_MDIICONARRANGE: + return "WM_MDIICONARRANGE" + case WM_MDIMAXIMIZE: + return "WM_MDIMAXIMIZE" + case WM_MDINEXT: + return "WM_MDINEXT" + case WM_MDIREFRESHMENU: + return "WM_MDIREFRESHMENU" + case WM_MDIRESTORE: + return "WM_MDIRESTORE" + case WM_MDISETMENU: + return "WM_MDISETMENU" + case WM_MDITILE: + return "WM_MDITILE" + case WM_MEASUREITEM: + return "WM_MEASUREITEM" + case WM_GETOBJECT: + return "WM_GETOBJECT" + case WM_CHANGEUISTATE: + return "WM_CHANGEUISTATE" + case WM_UPDATEUISTATE: + return "WM_UPDATEUISTATE" + case WM_QUERYUISTATE: + return "WM_QUERYUISTATE" + case WM_UNINITMENUPOPUP: + return "WM_UNINITMENUPOPUP" + case WM_MENURBUTTONUP: + return "WM_MENURBUTTONUP" + case WM_MENUCOMMAND: + return "WM_MENUCOMMAND" + case WM_MENUGETOBJECT: + return "WM_MENUGETOBJECT" + case WM_MENUDRAG: + return "WM_MENUDRAG" + case WM_APPCOMMAND: + return "WM_APPCOMMAND" + case WM_MENUCHAR: + return "WM_MENUCHAR" + case WM_MENUSELECT: + return "WM_MENUSELECT" + case WM_MOVE: + return "WM_MOVE" + case WM_MOVING: + return "WM_MOVING" + case WM_NCACTIVATE: + return "WM_NCACTIVATE" + case WM_NCCALCSIZE: + return "WM_NCCALCSIZE" + case WM_NCCREATE: + return "WM_NCCREATE" + case WM_NCDESTROY: + return "WM_NCDESTROY" + case WM_NCHITTEST: + return "WM_NCHITTEST" + case WM_NCLBUTTONDBLCLK: + return "WM_NCLBUTTONDBLCLK" + case WM_NCLBUTTONDOWN: + return "WM_NCLBUTTONDOWN" + case WM_NCLBUTTONUP: + return "WM_NCLBUTTONUP" + case WM_NCMBUTTONDBLCLK: + return "WM_NCMBUTTONDBLCLK" + case WM_NCMBUTTONDOWN: + return "WM_NCMBUTTONDOWN" + case WM_NCMBUTTONUP: + return "WM_NCMBUTTONUP" + case WM_NCXBUTTONDOWN: + return "WM_NCXBUTTONDOWN" + case WM_NCXBUTTONUP: + return "WM_NCXBUTTONUP" + case WM_NCXBUTTONDBLCLK: + return "WM_NCXBUTTONDBLCLK" + case WM_NCMOUSEHOVER: + return "WM_NCMOUSEHOVER" + case WM_NCMOUSELEAVE: + return "WM_NCMOUSELEAVE" + case WM_NCMOUSEMOVE: + return "WM_NCMOUSEMOVE" + case WM_NCPAINT: + return "WM_NCPAINT" + case WM_NCRBUTTONDBLCLK: + return "WM_NCRBUTTONDBLCLK" + case WM_NCRBUTTONDOWN: + return "WM_NCRBUTTONDOWN" + case WM_NCRBUTTONUP: + return "WM_NCRBUTTONUP" + case WM_NEXTDLGCTL: + return "WM_NEXTDLGCTL" + case WM_NEXTMENU: + return "WM_NEXTMENU" + case WM_NOTIFY: + return "WM_NOTIFY" + case WM_NOTIFYFORMAT: + return "WM_NOTIFYFORMAT" + case WM_NULL: + return "WM_NULL" + case WM_PAINT: + return "WM_PAINT" + case WM_PAINTCLIPBOARD: + return "WM_PAINTCLIPBOARD" + case WM_PAINTICON: + return "WM_PAINTICON" + case WM_PALETTECHANGED: + return "WM_PALETTECHANGED" + case WM_PALETTEISCHANGING: + return "WM_PALETTEISCHANGING" + case WM_PARENTNOTIFY: + return "WM_PARENTNOTIFY" + case WM_PASTE: + return "WM_PASTE" + case WM_PENWINFIRST: + return "WM_PENWINFIRST" + case WM_PENWINLAST: + return "WM_PENWINLAST" + case WM_POWER: + return "WM_POWER" + case WM_PRINT: + return "WM_PRINT" + case WM_PRINTCLIENT: + return "WM_PRINTCLIENT" + case WM_QUERYDRAGICON: + return "WM_QUERYDRAGICON" + case WM_QUERYENDSESSION: + return "WM_QUERYENDSESSION" + case WM_QUERYNEWPALETTE: + return "WM_QUERYNEWPALETTE" + case WM_QUERYOPEN: + return "WM_QUERYOPEN" + case WM_QUEUESYNC: + return "WM_QUEUESYNC" + case WM_QUIT: + return "WM_QUIT" + case WM_RENDERALLFORMATS: + return "WM_RENDERALLFORMATS" + case WM_RENDERFORMAT: + return "WM_RENDERFORMAT" + case WM_SETCURSOR: + return "WM_SETCURSOR" + case WM_SETFOCUS: + return "WM_SETFOCUS" + case WM_SETFONT: + return "WM_SETFONT" + case WM_SETHOTKEY: + return "WM_SETHOTKEY" + case WM_SETICON: + return "WM_SETICON" + case WM_SETREDRAW: + return "WM_SETREDRAW" + case WM_SETTEXT: + return "WM_SETTEXT" + case WM_SETTINGCHANGE: + return "WM_SETTINGCHANGE" + case WM_SHOWWINDOW: + return "WM_SHOWWINDOW" + case WM_SIZE: + return "WM_SIZE" + case WM_SIZECLIPBOARD: + return "WM_SIZECLIPBOARD" + case WM_SIZING: + return "WM_SIZING" + case WM_SPOOLERSTATUS: + return "WM_SPOOLERSTATUS" + case WM_STYLECHANGED: + return "WM_STYLECHANGED" + case WM_STYLECHANGING: + return "WM_STYLECHANGING" + case WM_SYSCHAR: + return "WM_SYSCHAR" + case WM_SYSCOLORCHANGE: + return "WM_SYSCOLORCHANGE" + case WM_SYSCOMMAND: + return "WM_SYSCOMMAND" + case WM_SYSDEADCHAR: + return "WM_SYSDEADCHAR" + case WM_SYSKEYDOWN: + return "WM_SYSKEYDOWN" + case WM_SYSKEYUP: + return "WM_SYSKEYUP" + case WM_TCARD: + return "WM_TCARD" + case WM_THEMECHANGED: + return "WM_THEMECHANGED" + case WM_TIMECHANGE: + return "WM_TIMECHANGE" + case WM_TIMER: + return "WM_TIMER" + case WM_UNDO: + return "WM_UNDO" + case WM_USER: + return "WM_USER" + case WM_USERCHANGED: + return "WM_USERCHANGED" + case WM_VKEYTOITEM: + return "WM_VKEYTOITEM" + case WM_VSCROLL: + return "WM_VSCROLL" + case WM_VSCROLLCLIPBOARD: + return "WM_VSCROLLCLIPBOARD" + case WM_WINDOWPOSCHANGED: + return "WM_WINDOWPOSCHANGED" + case WM_WINDOWPOSCHANGING: + return "WM_WINDOWPOSCHANGING" + case WM_KEYLAST: + return "WM_KEYLAST" + case WM_SYNCPAINT: + return "WM_SYNCPAINT" + case WM_MOUSEACTIVATE: + return "WM_MOUSEACTIVATE" + case WM_MOUSEMOVE: + return "WM_MOUSEMOVE" + case WM_LBUTTONDOWN: + return "WM_LBUTTONDOWN" + case WM_LBUTTONUP: + return "WM_LBUTTONUP" + case WM_LBUTTONDBLCLK: + return "WM_LBUTTONDBLCLK" + case WM_RBUTTONDOWN: + return "WM_RBUTTONDOWN" + case WM_RBUTTONUP: + return "WM_RBUTTONUP" + case WM_RBUTTONDBLCLK: + return "WM_RBUTTONDBLCLK" + case WM_MBUTTONDOWN: + return "WM_MBUTTONDOWN" + case WM_MBUTTONUP: + return "WM_MBUTTONUP" + case WM_MBUTTONDBLCLK: + return "WM_MBUTTONDBLCLK" + case WM_MOUSEWHEEL: + return "WM_MOUSEWHEEL" + case WM_XBUTTONDOWN: + return "WM_XBUTTONDOWN" + case WM_XBUTTONUP: + return "WM_XBUTTONUP" + case WM_MOUSELAST: + return "WM_MOUSELAST" + case WM_MOUSEHOVER: + return "WM_MOUSEHOVER" + case WM_MOUSELEAVE: + return "WM_MOUSELEAVE" + case WM_CLIPBOARDUPDATE: + return "WM_CLIPBOARDUPDATE" + default: + return fmt.Sprintf("0x%08x", msg) + } +} diff --git a/v3/pkg/w32/vars.go b/v3/pkg/w32/vars.go new file mode 100644 index 000000000..cb69f9d19 --- /dev/null +++ b/v3/pkg/w32/vars.go @@ -0,0 +1,16 @@ +//go:build windows + +/* + * Copyright (C) 2019 Tad Vizbaras. All Rights Reserved. + * Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved. + */ + +package w32 + +var ( + IID_NULL = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} + IID_IUnknown = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} + IID_IDispatch = &GUID{0x00020400, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} + IID_IConnectionPointContainer = &GUID{0xB196B284, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}} + IID_IConnectionPoint = &GUID{0xB196B286, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}} +) diff --git a/v3/pkg/w32/window.go b/v3/pkg/w32/window.go new file mode 100644 index 000000000..3e488a49c --- /dev/null +++ b/v3/pkg/w32/window.go @@ -0,0 +1,362 @@ +//go:build windows + +package w32 + +import ( + "fmt" + "github.com/samber/lo" + "strconv" + "strings" + "sync" + "syscall" + "unsafe" +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") + getSystemMenu = user32.NewProc("GetSystemMenu") + getMenuProc = user32.NewProc("GetMenu") + enableMenuItem = user32.NewProc("EnableMenuItem") + findWindow = user32.NewProc("FindWindowW") + sendMessage = user32.NewProc("SendMessageW") + vkKeyScan = user32.NewProc("VkKeyScanW") // Use W version for Unicode +) + +func VkKeyScan(ch uint16) uint16 { + ret, _, _ := syscall.SyscallN( + vkKeyScan.Addr(), + uintptr(ch), + ) + return uint16(ret) +} + +const ( + WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542 +) + +type COPYDATASTRUCT struct { + DwData uintptr + CbData uint32 + LpData uintptr +} + +var Fatal func(error) + +const ( + GCLP_HBRBACKGROUND int32 = -10 + GCLP_HICON int32 = -14 +) + +type WINDOWPOS struct { + HwndInsertAfter HWND + X int32 + Y int32 + Cx int32 + Cy int32 + Flags uint32 +} + +func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) error { + // -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) + // Also shows the caption buttons if transparent ant translucent but they don't work. + // 0: Adds the default frame styling but no aero shadow, does not show the caption buttons. + // 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons + // are shown if transparent ant translucent. + var margins MARGINS + if extend { + margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons + } + if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil { + return fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err) + } + return nil +} + +func IsVisible(hwnd uintptr) bool { + ret, _, _ := procIsWindowVisible.Call(hwnd) + return ret != 0 +} + +func IsWindowFullScreen(hwnd uintptr) bool { + wRect := GetWindowRect(hwnd) + m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY) + var mi MONITORINFO + mi.CbSize = uint32(unsafe.Sizeof(mi)) + if !GetMonitorInfo(m, &mi) { + return false + } + return wRect.Left == mi.RcMonitor.Left && + wRect.Top == mi.RcMonitor.Top && + wRect.Right == mi.RcMonitor.Right && + wRect.Bottom == mi.RcMonitor.Bottom +} + +func IsWindowMaximised(hwnd uintptr) bool { + style := uint32(getWindowLong(hwnd, GWL_STYLE)) + return style&WS_MAXIMIZE != 0 +} +func IsWindowMinimised(hwnd uintptr) bool { + style := uint32(getWindowLong(hwnd, GWL_STYLE)) + return style&WS_MINIMIZE != 0 +} + +func RestoreWindow(hwnd uintptr) { + showWindow(hwnd, SW_RESTORE) +} + +func ShowWindowMaximised(hwnd uintptr) { + showWindow(hwnd, SW_MAXIMIZE) +} +func ShowWindowMinimised(hwnd uintptr) { + showWindow(hwnd, SW_MINIMIZE) +} + +func SetApplicationIcon(hwnd uintptr, icon HICON) { + setClassLongPtr(hwnd, GCLP_HICON, icon) +} + +func SetBackgroundColour(hwnd uintptr, r, g, b uint8) { + col := uint32(r) | uint32(g)<<8 | uint32(b)<<16 + hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col)) + setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush) +} + +func IsWindowNormal(hwnd uintptr) bool { + return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd) +} + +func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool { + proc := procSetClassLongPtr + if strconv.IntSize == 32 { + /* + https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw + Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr. + When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function + + => We have to do this dynamically when directly calling the DLL procedures + */ + proc = procSetClassLong + } + + ret, _, _ := proc.Call( + hwnd, + uintptr(param), + val, + ) + return ret != 0 +} + +func getWindowLong(hwnd uintptr, index int) int32 { + ret, _, _ := procGetWindowLong.Call( + hwnd, + uintptr(index)) + + return int32(ret) +} + +func showWindow(hwnd uintptr, cmdshow int) bool { + ret, _, _ := procShowWindow.Call( + hwnd, + uintptr(cmdshow)) + return ret != 0 +} + +func stripNulls(str string) string { + // Split the string into substrings at each null character + substrings := strings.Split(str, "\x00") + + // Join the substrings back into a single string + strippedStr := strings.Join(substrings, "") + + return strippedStr +} + +func MustStringToUTF16Ptr(input string) *uint16 { + input = stripNulls(input) + result, err := syscall.UTF16PtrFromString(input) + if err != nil { + Fatal(err) + } + return result +} + +func MustStringToUTF16uintptr(input string) uintptr { + input = stripNulls(input) + ret := lo.Must(syscall.UTF16PtrFromString(input)) + return uintptr(unsafe.Pointer(ret)) +} + +func MustStringToUTF16(input string) []uint16 { + input = stripNulls(input) + return lo.Must(syscall.UTF16FromString(input)) +} + +func StringToUTF16(input string) ([]uint16, error) { + input = stripNulls(input) + return syscall.UTF16FromString(input) +} + +func CenterWindow(hwnd HWND) { + windowInfo := getWindowInfo(hwnd) + frameless := windowInfo.IsPopup() + + info := GetMonitorInfoForWindow(hwnd) + workRect := info.RcWork + screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2 + screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2 + var winRect *RECT + if !frameless { + winRect = GetWindowRect(hwnd) + } else { + winRect = GetClientRect(hwnd) + } + winWidth := winRect.Right - winRect.Left + winHeight := winRect.Bottom - winRect.Top + windowX := screenMiddleW - (winWidth / 2) + windowY := screenMiddleH - (winHeight / 2) + SetWindowPos(hwnd, HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), SWP_NOSIZE) +} + +func getWindowInfo(hwnd HWND) *WINDOWINFO { + var info WINDOWINFO + info.CbSize = uint32(unsafe.Sizeof(info)) + GetWindowInfo(hwnd, &info) + return &info +} + +func GetMonitorInfoForWindow(hwnd HWND) *MONITORINFO { + currentMonitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) + var info MONITORINFO + info.CbSize = uint32(unsafe.Sizeof(info)) + GetMonitorInfo(currentMonitor, &info) + return &info +} + +type WindowProc func(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr + +var windowClasses = make(map[string]HINSTANCE) +var windowClassesLock sync.Mutex + +func getWindowClass(name string) (HINSTANCE, bool) { + windowClassesLock.Lock() + defer windowClassesLock.Unlock() + result, exists := windowClasses[name] + return result, exists +} + +func setWindowClass(name string, instance HINSTANCE) { + windowClassesLock.Lock() + defer windowClassesLock.Unlock() + windowClasses[name] = instance +} + +func RegisterWindow(name string, proc WindowProc) (HINSTANCE, error) { + classInstance, exists := getWindowClass(name) + if exists { + return classInstance, nil + } + applicationInstance := GetModuleHandle("") + if applicationInstance == 0 { + return 0, fmt.Errorf("get module handle failed") + } + + var wc WNDCLASSEX + wc.Size = uint32(unsafe.Sizeof(wc)) + wc.WndProc = syscall.NewCallback(proc) + wc.Instance = applicationInstance + wc.Icon = LoadIconWithResourceID(0, uint16(IDI_APPLICATION)) + wc.Cursor = LoadCursorWithResourceID(0, uint16(IDC_ARROW)) + wc.Background = COLOR_BTNFACE + 1 + wc.ClassName = MustStringToUTF16Ptr(name) + + atom := RegisterClassEx(&wc) + if atom == 0 { + panic(syscall.GetLastError()) + } + + setWindowClass(name, applicationInstance) + + return applicationInstance, nil +} + +func FlashWindow(hwnd HWND, enabled bool) { + var flashInfo FLASHWINFO + flashInfo.CbSize = uint32(unsafe.Sizeof(flashInfo)) + flashInfo.Hwnd = hwnd + if enabled { + flashInfo.DwFlags = FLASHW_ALL | FLASHW_TIMERNOFG + } else { + flashInfo.DwFlags = FLASHW_STOP + } + _, _, _ = procFlashWindowEx.Call(uintptr(unsafe.Pointer(&flashInfo))) +} + +func EnumChildWindows(hwnd HWND, callback func(hwnd HWND, lparam LPARAM) LRESULT) LRESULT { + r, _, _ := procEnumChildWindows.Call(hwnd, syscall.NewCallback(callback), 0) + return r +} + +func DisableCloseButton(hwnd HWND) error { + hSysMenu, _, err := getSystemMenu.Call(hwnd, 0) + if hSysMenu == 0 { + return err + } + + r1, _, err := enableMenuItem.Call(hSysMenu, SC_CLOSE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED) + if r1 == 0 { + return err + } + + return nil +} + +func EnableCloseButton(hwnd HWND) error { + hSysMenu, _, err := getSystemMenu.Call(hwnd, 0) + if hSysMenu == 0 { + return err + } + + r1, _, err := enableMenuItem.Call(hSysMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED) + if r1 == 0 { + return err + } + + return nil +} + +func FindWindowW(className, windowName *uint16) HWND { + ret, _, _ := findWindow.Call( + uintptr(unsafe.Pointer(className)), + uintptr(unsafe.Pointer(windowName)), + ) + return HWND(ret) +} + +func SendMessageToWindow(hwnd HWND, msg string) { + // Convert data to UTF16 string + dataUTF16, err := StringToUTF16(msg) + if err != nil { + return + } + + // Prepare COPYDATASTRUCT + cds := COPYDATASTRUCT{ + DwData: WMCOPYDATA_SINGLE_INSTANCE_DATA, + CbData: uint32((len(dataUTF16) * 2) + 1), // +1 for null terminator + LpData: uintptr(unsafe.Pointer(&dataUTF16[0])), + } + + // Send message to first instance + _, _, _ = procSendMessage.Call( + hwnd, + WM_COPYDATA, + 0, + uintptr(unsafe.Pointer(&cds)), + ) +} + +// GetMenu retrieves a handle to the menu assigned to the specified window +func GetMenu(hwnd HWND) HMENU { + ret, _, _ := getMenuProc.Call(hwnd) + return ret +} diff --git a/v3/release-notes.txt b/v3/release-notes.txt new file mode 100644 index 000000000..156e62081 --- /dev/null +++ b/v3/release-notes.txt @@ -0,0 +1,2 @@ +## Fixed +- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450) \ No newline at end of file diff --git a/v3/release_notes.md b/v3/release_notes.md new file mode 100644 index 000000000..156e62081 --- /dev/null +++ b/v3/release_notes.md @@ -0,0 +1,2 @@ +## Fixed +- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450) \ No newline at end of file diff --git a/v3/scripts/validate-changelog.go b/v3/scripts/validate-changelog.go new file mode 100644 index 000000000..659285a20 --- /dev/null +++ b/v3/scripts/validate-changelog.go @@ -0,0 +1,270 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" +) + +func main() { + if len(os.Args) < 3 { + fmt.Println("Usage: go run validate-changelog.go ") + os.Exit(1) + } + + changelogPath := os.Args[1] + addedLinesPath := os.Args[2] + + // Read changelog + content, err := readFile(changelogPath) + if err != nil { + fmt.Printf("ERROR: Failed to read changelog: %v\n", err) + os.Exit(1) + } + + // Read the lines added in this PR + addedContent, err := readFile(addedLinesPath) + if err != nil { + fmt.Printf("ERROR: Failed to read PR added lines: %v\n", err) + os.Exit(1) + } + + addedLines := strings.Split(addedContent, "\n") + fmt.Printf("๐Ÿ“ Lines added in this PR: %d\n", len(addedLines)) + + // Parse changelog to find where added lines ended up + lines := strings.Split(content, "\n") + + // Find problematic entries - only check lines that were ADDED in this PR + var issues []Issue + currentSection := "" + + for lineNum, line := range lines { + // Track current section + if strings.HasPrefix(line, "## ") { + if strings.Contains(line, "[Unreleased]") { + currentSection = "Unreleased" + } else if strings.Contains(line, "v3.0.0-alpha") { + // Extract version from line like "## v3.0.0-alpha.10 - 2025-07-06" + parts := strings.Split(strings.TrimSpace(line[3:]), " - ") + if len(parts) >= 1 { + currentSection = strings.TrimSpace(parts[0]) + } + } + } + + // Check if this line was added in this PR AND is in a released version + if currentSection != "" && currentSection != "Unreleased" && + strings.HasPrefix(strings.TrimSpace(line), "- ") && + wasAddedInThisPR(line, addedLines) { + + issues = append(issues, Issue{ + Line: lineNum, + Content: strings.TrimSpace(line), + Section: currentSection, + Category: getCurrentCategory(lines, lineNum), + }) + fmt.Printf("๐Ÿšจ MISPLACED: Line added to released version %s: %s\n", currentSection, strings.TrimSpace(line)) + } + } + + if len(issues) == 0 { + fmt.Println("VALIDATION_RESULT=success") + fmt.Println("No misplaced changelog entries found โœ…") + return + } + + // Try to fix the issues + fmt.Printf("Found %d potentially misplaced entries:\n", len(issues)) + for _, issue := range issues { + fmt.Printf(" - Line %d in %s: %s\n", issue.Line+1, issue.Section, issue.Content) + } + + // Attempt automatic fix + fixed, err := attemptFix(content, issues, changelogPath) + if err != nil { + fmt.Printf("VALIDATION_RESULT=error\n") + fmt.Printf("ERROR: Failed to fix changelog: %v\n", err) + os.Exit(1) + } + + if fixed { + fmt.Println("VALIDATION_RESULT=fixed") + fmt.Println("โœ… Changelog has been automatically fixed") + } else { + fmt.Println("VALIDATION_RESULT=cannot_fix") + fmt.Println("โŒ Cannot automatically fix changelog issues") + os.Exit(1) + } +} + +type Issue struct { + Line int + Content string + Section string + Category string +} + +func wasAddedInThisPR(line string, addedLines []string) bool { + trimmedLine := strings.TrimSpace(line) + for _, addedLine := range addedLines { + trimmedAdded := strings.TrimSpace(addedLine) + if trimmedAdded == trimmedLine { + return true + } + if strings.Contains(trimmedAdded, trimmedLine) && len(trimmedAdded) > 0 { + return true + } + } + return false +} + +func getCurrentCategory(lines []string, lineNum int) string { + for i := lineNum - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if strings.HasPrefix(line, "### ") { + return strings.TrimSpace(line[4:]) + } + if strings.HasPrefix(line, "## ") && + !strings.Contains(line, "[Unreleased]") && + !strings.Contains(line, "v3.0.0-alpha") { + return strings.TrimSpace(line[3:]) + } + if strings.HasPrefix(line, "## ") && + (strings.Contains(line, "[Unreleased]") || strings.Contains(line, "v3.0.0-alpha")) { + break + } + } + return "Added" +} + +func attemptFix(content string, issues []Issue, outputPath string) (bool, error) { + lines := strings.Split(content, "\n") + + // Find unreleased section + unreleasedStart := -1 + unreleasedEnd := -1 + + for i, line := range lines { + if strings.Contains(line, "[Unreleased]") { + unreleasedStart = i + for j := i + 1; j < len(lines); j++ { + if strings.HasPrefix(lines[j], "## ") && !strings.Contains(lines[j], "[Unreleased]") { + unreleasedEnd = j + break + } + } + break + } + } + + if unreleasedStart == -1 { + return false, fmt.Errorf("Could not find [Unreleased] section") + } + + // Group issues by category + issuesByCategory := make(map[string][]Issue) + for _, issue := range issues { + issuesByCategory[issue.Category] = append(issuesByCategory[issue.Category], issue) + } + + // Remove issues from original locations (in reverse order) + var linesToRemove []int + for _, issue := range issues { + linesToRemove = append(linesToRemove, issue.Line) + } + + // Sort in reverse order + for i := 0; i < len(linesToRemove); i++ { + for j := i + 1; j < len(linesToRemove); j++ { + if linesToRemove[i] < linesToRemove[j] { + linesToRemove[i], linesToRemove[j] = linesToRemove[j], linesToRemove[i] + } + } + } + + // Remove lines + for _, lineNum := range linesToRemove { + lines = append(lines[:lineNum], lines[lineNum+1:]...) + } + + // Add entries to unreleased section + for category, categoryIssues := range issuesByCategory { + categoryFound := false + insertPos := unreleasedStart + 1 + + for i := unreleasedStart + 1; i < unreleasedEnd && i < len(lines); i++ { + if strings.Contains(lines[i], "### "+category) || strings.Contains(lines[i], "## "+category) { + categoryFound = true + for j := i + 1; j < unreleasedEnd && j < len(lines); j++ { + if strings.HasPrefix(lines[j], "### ") || strings.HasPrefix(lines[j], "## ") { + insertPos = j + break + } + if j == len(lines)-1 || j == unreleasedEnd-1 { + insertPos = j + 1 + break + } + } + break + } + } + + if !categoryFound { + if unreleasedEnd > 0 { + insertPos = unreleasedEnd + } else { + insertPos = unreleasedStart + 1 + } + + newLines := []string{ + "", + "### " + category, + "", + } + lines = append(lines[:insertPos], append(newLines, lines[insertPos:]...)...) + insertPos += len(newLines) + unreleasedEnd += len(newLines) + } + + // Add entries to the category + for _, issue := range categoryIssues { + lines = append(lines[:insertPos], append([]string{issue.Content}, lines[insertPos:]...)...) + insertPos++ + unreleasedEnd++ + } + } + + // Write back to file + newContent := strings.Join(lines, "\n") + return true, writeFile(outputPath, newContent) +} + +func readFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + var content strings.Builder + scanner := bufio.NewScanner(file) + for scanner.Scan() { + content.WriteString(scanner.Text()) + content.WriteString("\n") + } + + return content.String(), scanner.Err() +} + +func writeFile(path, content string) error { + dir := filepath.Dir(path) + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + return os.WriteFile(path, []byte(content), 0644) +} \ No newline at end of file diff --git a/v3/tasks/Taskfile.yml b/v3/tasks/Taskfile.yml new file mode 100644 index 000000000..a9a8505dd --- /dev/null +++ b/v3/tasks/Taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + generate:events: + dir: ./events + cmds: + - go run generate.go \ No newline at end of file diff --git a/v3/tasks/cleanup/cleanup.go b/v3/tasks/cleanup/cleanup.go new file mode 100644 index 000000000..f06813abf --- /dev/null +++ b/v3/tasks/cleanup/cleanup.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// CleanupPattern represents a cleanup rule +type CleanupPattern struct { + Type string // "prefix", "suffix", "exact" + Pattern string // The pattern to match + TargetFiles bool // true = target files, false = target directories + Description string // Description for logging +} + +// Patterns to clean up during test cleanup +var cleanupPatterns = []CleanupPattern{ + // Test binaries from examples + {Type: "prefix", Pattern: "testbuild-", TargetFiles: true, Description: "test binary"}, + + // Go test binaries + {Type: "suffix", Pattern: ".test", TargetFiles: true, Description: "Go test binary"}, + + // Package artifacts from packaging tests (only in internal/commands directory) + // Note: Only clean these from the commands directory, not from test temp directories + + // Test template directories from template tests + {Type: "prefix", Pattern: "test-template-", TargetFiles: false, Description: "test template directory"}, + + // CLI test binaries (files named exactly "appimage_testfiles") + {Type: "exact", Pattern: "appimage_testfiles", TargetFiles: true, Description: "CLI test binary"}, +} + +func main() { + fmt.Println("Starting cleanup...") + cleanedCount := 0 + + // Walk through all files and directories + err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil // Continue on errors + } + + // Skip if we're in the .git directory + if strings.Contains(path, ".git") { + return nil + } + + name := info.Name() + + // Check each cleanup pattern + for _, pattern := range cleanupPatterns { + shouldClean := false + + switch pattern.Type { + case "prefix": + shouldClean = strings.HasPrefix(name, pattern.Pattern) + case "suffix": + shouldClean = strings.HasSuffix(name, pattern.Pattern) + case "exact": + shouldClean = name == pattern.Pattern + } + + if shouldClean { + // Check if the pattern targets the correct type (file or directory) + if pattern.TargetFiles && info.Mode().IsRegular() { + // This pattern targets files and this is a file + fmt.Printf("Removing %s: %s\n", pattern.Description, path) + os.Remove(path) + cleanedCount++ + break // Don't check other patterns for this file + } else if !pattern.TargetFiles && info.IsDir() { + // This pattern targets directories and this is a directory + fmt.Printf("Removing %s: %s\n", pattern.Description, path) + os.RemoveAll(path) + cleanedCount++ + return filepath.SkipDir // Don't recurse into removed directory + } + // If the pattern matches but the file type doesn't match TargetFiles, continue checking other patterns + } + } + + return nil + }) + + if err != nil { + fmt.Printf("Error during cleanup: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Cleanup completed. Removed %d items.\n", cleanedCount) +} \ No newline at end of file diff --git a/v3/tasks/contribs/main.go b/v3/tasks/contribs/main.go new file mode 100644 index 000000000..64fba1968 --- /dev/null +++ b/v3/tasks/contribs/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "log" + "os/exec" + "strings" +) + +func main() { + cmd := exec.Command("npx", "all-contributors-cli", "check") + //cmd.Stdin = strings.NewReader("some input") + var out strings.Builder + cmd.Stdout = &out + err := cmd.Run() + missingSplit := strings.Split(out.String(), "\n") + if len(missingSplit) < 2 { + log.Fatal(out.String()) + } + missing := missingSplit[1] + missing = strings.TrimSpace(missing) + // Split on comma + for _, contrib := range strings.Split(missing, ",") { + // Trim whitespace + contrib = strings.TrimSpace(contrib) + if contrib == "dependabot[bot]" || contrib == "" { + continue + } + // Add contributor + cmd := exec.Command("npx", "all-contributors-cli", "add", contrib, "code") + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + } + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/tasks/events/generate.go b/v3/tasks/events/generate.go new file mode 100644 index 000000000..e9e3ec8d8 --- /dev/null +++ b/v3/tasks/events/generate.go @@ -0,0 +1,381 @@ +package main + +import ( + "bytes" + "os" + "strconv" + "strings" +) + +const eventsGo = `package events + +type ApplicationEventType uint +type WindowEventType uint + +var Common = newCommonEvents() + +type commonEvents struct { +$$COMMONEVENTSDECL} + +func newCommonEvents() commonEvents { + return commonEvents{ +$$COMMONEVENTSVALUES } +} + +var Linux = newLinuxEvents() + +type linuxEvents struct { +$$LINUXEVENTSDECL} + +func newLinuxEvents() linuxEvents { + return linuxEvents{ +$$LINUXEVENTSVALUES } +} + +var Mac = newMacEvents() + +type macEvents struct { +$$MACEVENTSDECL} + +func newMacEvents() macEvents { + return macEvents{ +$$MACEVENTSVALUES } +} + +var Windows = newWindowsEvents() + +type windowsEvents struct { +$$WINDOWSEVENTSDECL} + +func newWindowsEvents() windowsEvents { + return windowsEvents{ +$$WINDOWSEVENTSVALUES } +} + +func JSEvent(event uint) string { + return eventToJS[event] +} + +var eventToJS = map[uint]string{ +$$EVENTTOJS} + +` + +const darwinEventsH = `//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +$$CHEADEREVENTS + +#endif` + +const linuxEventsH = `//go:build linux + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int, void* data); +extern void processWindowEvent(unsigned int, unsigned int); + +$$CHEADEREVENTS + +#endif` + +const eventsTS = `/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ ` + "`" + `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร‚ MODIWL +// This file is automatically generated. DO NOT EDIT + +export const Types = Object.freeze({ + Windows: Object.freeze({ +$$WINDOWSJSEVENTS }), + Mac: Object.freeze({ +$$MACJSEVENTS }), + Linux: Object.freeze({ +$$LINUXJSEVENTS }), + Common: Object.freeze({ +$$COMMONJSEVENTS }), +}); +` + +func main() { + eventNames, err := os.ReadFile("../../pkg/events/events.txt") + if err != nil { + panic(err) + } + + linuxEventsDecl := bytes.NewBufferString("") + linuxEventsValues := bytes.NewBufferString("") + linuxCHeaderEvents := bytes.NewBufferString("") + + macEventsDecl := bytes.NewBufferString("") + macEventsValues := bytes.NewBufferString("") + macCHeaderEvents := bytes.NewBufferString("") + windowDelegateEvents := bytes.NewBufferString("") + applicationDelegateEvents := bytes.NewBufferString("") + webviewDelegateEvents := bytes.NewBufferString("") + + windowsEventsDecl := bytes.NewBufferString("") + windowsEventsValues := bytes.NewBufferString("") + + commonEventsDecl := bytes.NewBufferString("") + commonEventsValues := bytes.NewBufferString("") + + linuxTSEvents := bytes.NewBufferString("") + macTSEvents := bytes.NewBufferString("") + windowsTSEvents := bytes.NewBufferString("") + commonTSEvents := bytes.NewBufferString("") + + eventToJS := bytes.NewBufferString("") + + var id int + // var maxLinuxEvents int + var maxMacEvents int + var maxLinuxEvents int + var line []byte + // Loop over each line in the file + for id, line = range bytes.Split(eventNames, []byte{'\n'}) { + + // First 1024 is reserved + id = id + 1024 + + // Skip empty lines + if len(line) == 0 { + continue + } + + // split on the colon + split := bytes.Split(line, []byte{':'}) + platform := strings.TrimSpace(string(split[0])) + event := strings.TrimSpace(string(split[1])) + var ignoreEvent bool + if strings.HasSuffix(event, "!") { + event = strings.TrimSuffix(event, "!") + ignoreEvent = true + } + // Strip last byte of line if it's a "!" character + if line[len(line)-1] == '!' { + line = line[:len(line)-1] + } + + // Title case the event name + eventTitle := string(bytes.ToUpper([]byte{event[0]})) + event[1:] + // delegate function name has a lowercase first character + delegateEventFunction := string(bytes.ToLower([]byte{event[0]})) + event[1:] + + // Add to buffer + switch platform { + case "linux": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + linuxEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + linuxEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + linuxTSEvents.WriteString("\t\t" + event + ": \"linux:" + event + "\",\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + event + "\",\n") + maxLinuxEvents = id + linuxCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n") + case "mac": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + macTSEvents.WriteString("\t\t" + event + ": \"mac:" + event + "\",\n") + macCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + event + "\",\n") + maxMacEvents = id + if ignoreEvent { + continue + } + // Check if this is a window event + if strings.HasPrefix(event, "Window") { + windowDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification { + if( hasListeners(Event` + eventTitle + `) ) { + processWindowEvent(self.windowId, Event` + eventTitle + `); + } +} + +`) + } + // Check if this is a webview event + if strings.HasPrefix(event, "WebView") { + webViewFunction := strings.TrimPrefix(event, "WebView") + webViewFunction = string(bytes.ToLower([]byte{webViewFunction[0]})) + webViewFunction[1:] + webviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webview ` + webViewFunction + `:(WKNavigation *)navigation { + if( hasListeners(Event` + eventTitle + `) ) { + processWindowEvent(self.windowId, Event` + eventTitle + `); + } +} + +`) + } + if strings.HasPrefix(event, "Application") { + applicationDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification { + if( hasListeners(Event` + eventTitle + `) ) { + processApplicationEvent(Event` + eventTitle + `, NULL); + } +} + +`) + } + case "common": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + commonEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + commonEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + commonTSEvents.WriteString("\t\t" + event + ": \"common:" + event + "\",\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + event + "\",\n") + case "windows": + eventType := "ApplicationEventType" + if strings.HasPrefix(event, "Window") { + eventType = "WindowEventType" + } + if strings.HasPrefix(event, "WebView") { + eventType = "WindowEventType" + } + windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n") + windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n") + windowsTSEvents.WriteString("\t\t" + event + ": \"windows:" + event + "\",\n") + eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + event + "\",\n") + } + } + + macCHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxMacEvents+1) + "\n") + linuxCHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxLinuxEvents+1) + "\n") + + // Save the eventsGo template substituting the values and decls + templateToWrite := strings.ReplaceAll(eventsGo, "$$LINUXEVENTSDECL", linuxEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXEVENTSVALUES", linuxEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSDECL", macEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String()) + + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSDECL", windowsEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSVALUES", windowsEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSDECL", commonEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSVALUES", commonEventsValues.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$EVENTTOJS", eventToJS.String()) + err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the eventsTS template substituting the values and decls + templateToWrite = strings.ReplaceAll(eventsTS, "$$MACJSEVENTS", macTSEvents.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSJSEVENTS", windowsTSEvents.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXJSEVENTS", linuxTSEvents.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONJSEVENTS", commonTSEvents.String()) + err = os.WriteFile("../../internal/runtime/desktop/@wailsio/runtime/src/event_types.ts", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the darwinEventsH template substituting the values and decls + templateToWrite = strings.ReplaceAll(darwinEventsH, "$$CHEADEREVENTS", macCHeaderEvents.String()) + err = os.WriteFile("../../pkg/events/events_darwin.h", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the linuxEventsH template substituting the values and decls + templateToWrite = strings.ReplaceAll(linuxEventsH, "$$CHEADEREVENTS", linuxCHeaderEvents.String()) + err = os.WriteFile("../../pkg/events/events_linux.h", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Load the window_delegate.m file + windowDelegate, err := os.ReadFile("../../pkg/application/webview_window_darwin.m") + if err != nil { + panic(err) + } + // iterate over the lines until we reach a line that says "// GENERATED EVENTS START" + // then we insert the events + // then we iterate until we reach a line that says "// GENERATED EVENTS END" + // then we write the file + var buffer bytes.Buffer + var inGeneratedEvents bool + for _, line := range bytes.Split(windowDelegate, []byte{'\n'}) { + if bytes.Contains(line, []byte("// GENERATED EVENTS START")) { + inGeneratedEvents = true + buffer.WriteString("// GENERATED EVENTS START\n") + buffer.WriteString(windowDelegateEvents.String()) + buffer.WriteString(webviewDelegateEvents.String()) + continue + } + if bytes.Contains(line, []byte("// GENERATED EVENTS END")) { + inGeneratedEvents = false + buffer.WriteString("// GENERATED EVENTS END\n") + continue + } + if !inGeneratedEvents { + if len(line) > 0 { + buffer.Write(line) + buffer.WriteString("\n") + } + } + } + err = os.WriteFile("../../pkg/application/webview_window_darwin.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } + + // Load the app_delegate.m file + appDelegate, err := os.ReadFile("../../pkg/application/application_darwin_delegate.m") + if err != nil { + panic(err) + } + // iterate over the lines until we reach a line that says "// GENERATED EVENTS START" + // then we insert the events + // then we iterate until we reach a line that says "// GENERATED EVENTS END" + // then we write the file + buffer.Reset() + for _, line := range bytes.Split(appDelegate, []byte{'\n'}) { + if bytes.Contains(line, []byte("// GENERATED EVENTS START")) { + inGeneratedEvents = true + buffer.WriteString("// GENERATED EVENTS START\n") + buffer.WriteString(applicationDelegateEvents.String()) + continue + } + if bytes.Contains(line, []byte("// GENERATED EVENTS END")) { + inGeneratedEvents = false + buffer.WriteString("// GENERATED EVENTS END\n") + continue + } + if !inGeneratedEvents { + if len(line) > 0 { + buffer.Write(line) + buffer.WriteString("\n") + } + } + } + err = os.WriteFile("../../pkg/application/application_darwin_delegate.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } +} diff --git a/v3/tasks/events/go.mod b/v3/tasks/events/go.mod new file mode 100644 index 000000000..55ed969ca --- /dev/null +++ b/v3/tasks/events/go.mod @@ -0,0 +1,3 @@ +module events + +go 1.24 diff --git a/v3/tasks/release/RELEASE_NOTES_CREATION.md b/v3/tasks/release/RELEASE_NOTES_CREATION.md new file mode 100644 index 000000000..0fe04d7e0 --- /dev/null +++ b/v3/tasks/release/RELEASE_NOTES_CREATION.md @@ -0,0 +1,118 @@ +# Release Notes Creation Documentation + +## Overview + +The `release.go` script now supports a `--create-release-notes` flag that extracts changelog content from `UNRELEASED_CHANGELOG.md` and creates a clean `release_notes.md` file suitable for GitHub releases. + +## How It Works + +### 1. Command Usage + +```bash +go run release.go --create-release-notes [output_path] +``` + +- `output_path` is optional, defaults to `../../release_notes.md` +- The script expects `UNRELEASED_CHANGELOG.md` to be at `../../UNRELEASED_CHANGELOG.md` (relative to the script location) + +### 2. Extraction Process + +The `extractChangelogContent()` function: + +1. Reads the UNRELEASED_CHANGELOG.md file +2. Filters out: + - The main "# Unreleased Changes" header + - HTML comments (``) + - Empty sections (sections with no bullet points) + - Everything after the `---` separator (example entries) +3. Preserves: + - Section headers (## Added, ## Changed, etc.) that have content + - Bullet points with actual text (both `-` and `*` styles) + - Proper spacing between sections + +### 3. File Structure Expected + +```markdown +# Unreleased Changes + + + +## Added + +- Actual content preserved +- More content + +## Changed + + +## Fixed +- Bug fixes included + +--- + +### Example Entries: +Everything after the --- is ignored +``` + +### 4. Output Format + +The generated `release_notes.md` contains only the actual changelog entries: + +```markdown +## Added +- Actual content preserved +- More content + +## Fixed +- Bug fixes included +``` + +## Testing Results + +### โœ… Successful Tests + +1. **Valid Content Extraction**: Successfully extracts and formats changelog entries +2. **Empty Changelog Detection**: Properly fails when no content exists +3. **Comment Filtering**: Correctly removes HTML comments +4. **Mixed Bullet Styles**: Handles both `-` and `*` bullet points +5. **Custom Output Path**: Supports specifying custom output file location +6. **Flag Compatibility**: Works with `--check-only` and `--extract-changelog` flags + +### Test Commands Run + +```bash +# Create release notes with default path +go run release.go --create-release-notes + +# Create with custom path +go run release.go --create-release-notes /path/to/output.md + +# Check if content exists +go run release.go --check-only + +# Extract content to stdout +go run release.go --extract-changelog +``` + +## Integration with GitHub Workflow + +The nightly release workflow should: + +1. Run `go run release.go --create-release-notes` before the main release task +2. Use the generated `release_notes.md` for the GitHub release body +3. The main release task will clear UNRELEASED_CHANGELOG.md after processing + +## Error Handling + +The script will exit with status 1 if: +- UNRELEASED_CHANGELOG.md doesn't exist +- No actual content is found (only template/comments) +- File write operations fail + +## Benefits + +1. **Separation of Concerns**: Changelog extraction happens before the file is cleared +2. **Clean Output**: No template text or comments in release notes +3. **Testable**: Can be run and tested independently +4. **Flexible**: Supports custom output paths +5. **Consistent**: Same extraction logic used by all flags \ No newline at end of file diff --git a/v3/tasks/release/release.go b/v3/tasks/release/release.go new file mode 100644 index 000000000..dc584a570 --- /dev/null +++ b/v3/tasks/release/release.go @@ -0,0 +1,482 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "strings" + "time" +) + +const ( + versionFile = "../../internal/version/version.txt" + changelogFile = "../../../docs/src/content/docs/changelog.mdx" +) + +var ( + unreleasedChangelogFile = "../../UNRELEASED_CHANGELOG.md" +) + +func checkError(err error) { + if err != nil { + println(err.Error()) + os.Exit(1) + } +} + +// getUnreleasedChangelogTemplate returns the template content for UNRELEASED_CHANGELOG.md +func getUnreleasedChangelogTemplate() string { + return `# Unreleased Changes + + + +## Added + + +## Changed + + +## Fixed + + +## Deprecated + + +## Removed + + +## Security + + +--- + +### Example Entries: + +**Added:** +- Add support for custom window icons in application options +- Add new ` + "`SetWindowIcon()`" + ` method to runtime API (#1234) + +**Changed:** +- Update minimum Go version requirement to 1.21 +- Improve error messages for invalid configuration files + +**Fixed:** +- Fix memory leak in event system during window close operations (#5678) +- Fix crash when using context menus on Linux with Wayland + +**Security:** +- Update dependencies to address CVE-2024-12345 in third-party library +` +} + +// clearUnreleasedChangelog clears the UNRELEASED_CHANGELOG.md file and resets it with the template +func clearUnreleasedChangelog() error { + template := getUnreleasedChangelogTemplate() + + // Write the template back to the file + err := os.WriteFile(unreleasedChangelogFile, []byte(template), 0o644) + if err != nil { + return fmt.Errorf("failed to reset UNRELEASED_CHANGELOG.md: %w", err) + } + + fmt.Printf("Successfully reset %s with template content\n", unreleasedChangelogFile) + return nil +} + +// extractChangelogContent extracts the actual changelog content from UNRELEASED_CHANGELOG.md +// It returns the content between the section headers and the example section +func extractChangelogContent() (string, error) { + content, err := os.ReadFile(unreleasedChangelogFile) + if err != nil { + return "", fmt.Errorf("failed to read %s: %w", unreleasedChangelogFile, err) + } + + contentStr := string(content) + lines := strings.Split(contentStr, "\n") + + var result []string + var inExampleSection bool + var inCommentBlock bool + var hasActualContent bool + var currentSection string + + for i, line := range lines { + trimmedLine := strings.TrimSpace(line) + + // Track comment blocks (handle multi-line comments) + if strings.Contains(line, "") { + inCommentBlock = false + } + continue + } + if inCommentBlock { + if strings.Contains(line, "-->") { + inCommentBlock = false + } + continue + } + + // Skip the main title + if strings.HasPrefix(trimmedLine, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(trimmedLine, "---") || strings.HasPrefix(trimmedLine, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Handle section headers + if strings.HasPrefix(trimmedLine, "##") { + currentSection = trimmedLine + // Only include section headers that have content after them + // We'll add it later if we find content + continue + } + + // Handle bullet points + if strings.HasPrefix(trimmedLine, "-") || strings.HasPrefix(trimmedLine, "*") { + // Check if this is actual content (not empty) + content := strings.TrimSpace(trimmedLine[1:]) + if content != "" { + // If this is the first content in a section, add the section header first + if currentSection != "" { + // Only add empty line if this isn't the first section + if len(result) > 0 { + result = append(result, "") + } + result = append(result, currentSection) + currentSection = "" // Reset so we don't add it again + } + result = append(result, line) + hasActualContent = true + } + } else if trimmedLine != "" && !strings.HasPrefix(trimmedLine, " + +## Added +- New feature 1 +- New feature 2 + +## Changed +- Changed item 1 + +## Fixed +- Bug fix 1 +- Bug fix 2 + +## Deprecated +- Deprecated feature + +## Removed +- Removed feature + +## Security +- Security fix + +--- + +### Example Entries: + +**Added:** +- Example entry +`, + expected: `## Added +- New feature 1 +- New feature 2 + +## Changed +- Changed item 1 + +## Fixed +- Bug fix 1 +- Bug fix 2 + +## Deprecated +- Deprecated feature + +## Removed +- Removed feature + +## Security +- Security fix`, + wantErr: false, + }, + { + name: "Only Added section with content", + content: `# Unreleased Changes + +## Added + +- Add Windows dark theme support +- Add new flag to release script + +## Changed + + +## Fixed + + +--- +### Example Entries: +`, + expected: `## Added +- Add Windows dark theme support +- Add new flag to release script`, + wantErr: false, + }, + { + name: "Empty sections should not be included", + content: `# Unreleased Changes + +## Added + + +## Changed + +- Update Go version to 1.23 + +## Fixed + + +--- +`, + expected: `## Changed +- Update Go version to 1.23`, + wantErr: false, + }, + { + name: "No content returns empty string", + content: `# Unreleased Changes + +## Added + + +## Changed + + +## Fixed + + +--- +`, + expected: "", + wantErr: false, + }, + { + name: "Comments should be excluded", + content: `# Unreleased Changes + + + +## Added + +- Real content here + +- More content + +--- +`, + expected: `## Added +- Real content here +- More content`, + wantErr: false, + }, + { + name: "Multi-line comments handled correctly", + content: `# Unreleased Changes + + + +## Added +- Feature 1 + +- Feature 2 + +--- +`, + expected: `## Added +- Feature 1 +- Feature 2`, + wantErr: false, + }, + { + name: "Mixed bullet point styles", + content: `# Unreleased Changes + +## Added +- Dash bullet point +* Asterisk bullet point +- Another dash + +--- +`, + expected: `## Added +- Dash bullet point +* Asterisk bullet point +- Another dash`, + wantErr: false, + }, + { + name: "Trailing empty lines removed", + content: `# Unreleased Changes + +## Added +- Feature 1 + + +## Changed +- Change 1 + + + +--- +`, + expected: `## Added +- Feature 1 + +## Changed +- Change 1`, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary file + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "UNRELEASED_CHANGELOG.md") + err := os.WriteFile(tmpFile, []byte(tt.content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Save original path and update it + originalPath := unreleasedChangelogFile + unreleasedChangelogFile = tmpFile + defer func() { + unreleasedChangelogFile = originalPath + }() + + // Test the function + got, err := extractChangelogContent() + if (err != nil) != tt.wantErr { + t.Errorf("extractChangelogContent() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if got != tt.expected { + t.Errorf("extractChangelogContent() = %q, want %q", got, tt.expected) + } + }) + } +} + +// TestCreateReleaseNotes tests the --create-release-notes functionality +func TestCreateReleaseNotes(t *testing.T) { + tests := []struct { + name string + changelogContent string + expectSuccess bool + expectedNotes string + }{ + { + name: "Valid changelog creates release notes", + changelogContent: `# Unreleased Changes + +## Added +- New feature X +- New feature Y + +## Fixed +- Bug fix A + +--- +### Examples: +`, + expectSuccess: true, + expectedNotes: `## Added +- New feature X +- New feature Y + +## Fixed +- Bug fix A`, + }, + { + name: "Empty changelog fails", + changelogContent: `# Unreleased Changes + +## Added + + +## Changed + + +--- +`, + expectSuccess: false, + expectedNotes: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temporary directory + tmpDir := t.TempDir() + + // Create UNRELEASED_CHANGELOG.md + changelogPath := filepath.Join(tmpDir, "UNRELEASED_CHANGELOG.md") + err := os.WriteFile(changelogPath, []byte(tt.changelogContent), 0644) + if err != nil { + t.Fatalf("Failed to create changelog file: %v", err) + } + + // Create release notes path + releaseNotesPath := filepath.Join(tmpDir, "release_notes.md") + + // Save original path + originalPath := unreleasedChangelogFile + unreleasedChangelogFile = changelogPath + defer func() { + unreleasedChangelogFile = originalPath + }() + + // Test the create release notes flow + content, err := extractChangelogContent() + if err != nil && tt.expectSuccess { + t.Fatalf("Failed to extract content: %v", err) + } + + if tt.expectSuccess { + if content == "" { + t.Error("Expected content but got empty string") + } else { + // Write the release notes + err = os.WriteFile(releaseNotesPath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to write release notes: %v", err) + } + + // Verify the file was created + data, err := os.ReadFile(releaseNotesPath) + if err != nil { + t.Fatalf("Failed to read release notes: %v", err) + } + + if string(data) != tt.expectedNotes { + t.Errorf("Release notes = %q, want %q", string(data), tt.expectedNotes) + } + } + } else { + if content != "" { + t.Errorf("Expected no content but got: %q", content) + } + } + }) + } +} + +// TestHasUnreleasedContent tests the hasUnreleasedContent function +func TestHasUnreleasedContent(t *testing.T) { + tests := []struct { + name string + content string + expected bool + }{ + { + name: "Has content", + content: `# Unreleased Changes + +## Added +- New feature + +--- +`, + expected: true, + }, + { + name: "No content", + content: `# Unreleased Changes + +## Added + + +## Changed + + +--- +`, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temporary file + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "UNRELEASED_CHANGELOG.md") + err := os.WriteFile(tmpFile, []byte(tt.content), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Save original path + originalPath := unreleasedChangelogFile + unreleasedChangelogFile = tmpFile + defer func() { + unreleasedChangelogFile = originalPath + }() + + // Test the function + got, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() error = %v", err) + } + + if got != tt.expected { + t.Errorf("hasUnreleasedContent() = %v, want %v", got, tt.expected) + } + }) + } +} + +// TestVersionIncrement tests version incrementing logic +func TestVersionIncrement(t *testing.T) { + tests := []struct { + name string + currentVersion string + expectedNext string + }{ + { + name: "Alpha version increment", + currentVersion: "v3.0.0-alpha.15", + expectedNext: "v3.0.0-alpha.16", + }, + { + name: "Beta version increment", + currentVersion: "v3.0.0-beta.5", + expectedNext: "v3.0.0-beta.6", + }, + { + name: "Regular version increment", + currentVersion: "v3.0.0", + expectedNext: "v3.0.1", + }, + { + name: "Patch version increment", + currentVersion: "v3.1.5", + expectedNext: "v3.1.6", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test incrementPatchVersion for regular versions + if !strings.Contains(tt.currentVersion, "-") { + got := incrementPatchVersion(tt.currentVersion) + // Note: incrementPatchVersion writes to file, so we just check the return value + if got != tt.expectedNext { + t.Errorf("incrementPatchVersion(%s) = %s, want %s", tt.currentVersion, got, tt.expectedNext) + } + } + }) + } +} + +// TestClearUnreleasedChangelog tests the changelog reset functionality +func TestClearUnreleasedChangelog(t *testing.T) { + // Create temporary file + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "UNRELEASED_CHANGELOG.md") + + // Write some content + err := os.WriteFile(tmpFile, []byte("Some content"), 0644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Save original path + originalPath := unreleasedChangelogFile + unreleasedChangelogFile = tmpFile + defer func() { + unreleasedChangelogFile = originalPath + }() + + // Clear the changelog + err = clearUnreleasedChangelog() + if err != nil { + t.Fatalf("clearUnreleasedChangelog() error = %v", err) + } + + // Read the file + content, err := os.ReadFile(tmpFile) + if err != nil { + t.Fatalf("Failed to read cleared file: %v", err) + } + + // Check it contains the template + if !strings.Contains(string(content), "# Unreleased Changes") { + t.Error("Cleared file doesn't contain template header") + } + if !strings.Contains(string(content), "## Added") { + t.Error("Cleared file doesn't contain Added section") + } + if !strings.Contains(string(content), "### Example Entries:") { + t.Error("Cleared file doesn't contain example section") + } +} \ No newline at end of file diff --git a/v3/tasks/release/release_test.go b/v3/tasks/release/release_test.go new file mode 100644 index 000000000..e598446fc --- /dev/null +++ b/v3/tasks/release/release_test.go @@ -0,0 +1,1043 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" +) + +// setupTestEnvironment creates a proper directory structure for tests +// It returns the cleanup function and the project root directory +func setupTestEnvironment(t *testing.T) (cleanup func(), projectRoot string) { + // Save current directory + originalDir, _ := os.Getwd() + + // Create a temporary directory for testing + tmpDir := t.TempDir() + + // Create the wails project structure within temp directory + projectRoot = filepath.Join(tmpDir, "wails") + v3Dir := filepath.Join(projectRoot, "v3") + releaseDir := filepath.Join(v3Dir, "tasks", "release") + + // Create all necessary directories + err := os.MkdirAll(releaseDir, 0755) + if err != nil { + t.Fatalf("Failed to create test directory structure: %v", err) + } + + // Change to the release directory (where the script would run from) + os.Chdir(releaseDir) + + // Return cleanup function and project root + cleanup = func() { + os.Chdir(originalDir) + } + return cleanup, projectRoot +} + +func TestExtractChangelogContent_EmptySections(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a test file with only one section having content + testContent := `# Unreleased Changes + + + +## Added + + +## Changed + + +## Fixed +- Fix critical bug in the system + +## Deprecated + + +## Removed + + +## Security + + +--- + +### Example Entries: + +**Added:** +- Example entry that should not be included` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Extract the content + content, err := extractChangelogContent() + if err != nil { + t.Fatalf("extractChangelogContent() failed: %v", err) + } + + // Verify we got content + if content == "" { + t.Fatal("Expected to extract content, but got empty string") + } + + // Verify ONLY the Fixed section is included (the only one with content) + if !strings.Contains(content, "## Fixed") { + t.Error("Expected to find '## Fixed' section header") + } + + if !strings.Contains(content, "Fix critical bug in the system") { + t.Error("Expected to find the Fixed content") + } + + // Verify empty sections are NOT included + sections := []string{"## Added", "## Changed", "## Deprecated", "## Removed", "## Security"} + for _, section := range sections { + if strings.Contains(content, section) { + t.Errorf("Expected NOT to find empty section '%s'", section) + } + } + + // Verify comments are not included + if strings.Contains(content, "") { + t.Error("Expected NOT to find HTML comments") + } + + // Verify example content is not included + if strings.Contains(content, "Example entry that should not be included") { + t.Error("Expected NOT to find example content") + } +} + +func TestExtractChangelogContent_AllEmpty(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a test file with all empty sections (just the template) + testContent := getUnreleasedChangelogTemplate() + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Extract the content + content, err := extractChangelogContent() + if err != nil { + t.Fatalf("extractChangelogContent() failed: %v", err) + } + + // Verify we got empty string (no content) + if content != "" { + t.Fatalf("Expected empty string for template-only file, got: %s", content) + } +} + +func TestExtractChangelogContent_MixedSections(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a test file with some sections having content, others empty + testContent := `# Unreleased Changes + +## Added +- New feature A +- New feature B + +## Changed + + +## Fixed + + +## Deprecated +- Deprecated feature X + +## Removed + + +## Security +- Security fix for CVE-2024-1234` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Extract the content + content, err := extractChangelogContent() + if err != nil { + t.Fatalf("extractChangelogContent() failed: %v", err) + } + + // Verify we got content + if content == "" { + t.Fatal("Expected to extract content, but got empty string") + } + + // Verify sections WITH content are included + if !strings.Contains(content, "## Added") { + t.Error("Expected to find '## Added' section header") + } + if !strings.Contains(content, "New feature A") { + t.Error("Expected to find Added content") + } + + if !strings.Contains(content, "## Deprecated") { + t.Error("Expected to find '## Deprecated' section header") + } + if !strings.Contains(content, "Deprecated feature X") { + t.Error("Expected to find Deprecated content") + } + + if !strings.Contains(content, "## Security") { + t.Error("Expected to find '## Security' section header") + } + if !strings.Contains(content, "Security fix for CVE-2024-1234") { + t.Error("Expected to find Security content") + } + + // Verify empty sections are NOT included + emptyHeaders := []string{"## Changed", "## Fixed", "## Removed"} + for _, header := range emptyHeaders { + if strings.Contains(content, header) { + t.Errorf("Expected NOT to find empty section '%s'", header) + } + } + + // Print the extracted content for debugging + t.Logf("Extracted content:\n%s", content) +} + +func TestGetUnreleasedChangelogTemplate(t *testing.T) { + template := getUnreleasedChangelogTemplate() + + // Check that template contains required sections + requiredSections := []string{ + "# Unreleased Changes", + "## Added", + "## Changed", + "## Fixed", + "## Deprecated", + "## Removed", + "## Security", + "### Example Entries", + } + + for _, section := range requiredSections { + if !strings.Contains(template, section) { + t.Errorf("Template missing required section: %s", section) + } + } + + // Check that template contains guidelines + if !strings.Contains(template, "Guidelines:") { + t.Error("Template missing guidelines section") + } + + // Check that template contains example entries + if !strings.Contains(template, "Add support for custom window icons") { + t.Error("Template missing example entries") + } +} + +func TestHasUnreleasedContent_WithContent(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a file with actual content + testContent := `# Unreleased Changes + +## Added +- Add new feature for testing +- Add another important feature + +## Fixed +- Fix critical bug in system` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + + if !hasContent { + t.Error("Expected hasUnreleasedContent() to return true for file with content") + } +} + +func TestHasUnreleasedContent_WithoutContent(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a file with only template content (no actual entries) + template := getUnreleasedChangelogTemplate() + err := os.WriteFile(unreleasedChangelogFile, []byte(template), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + + if hasContent { + t.Error("Expected hasUnreleasedContent() to return false for template-only file") + } +} + +func TestHasUnreleasedContent_WithEmptyBullets(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a file with empty bullet points + testContent := `# Unreleased Changes + +## Added +- +- + +## Fixed +` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + + if hasContent { + t.Error("Expected hasUnreleasedContent() to return false for file with empty bullets") + } +} + +func TestHasUnreleasedContent_NonexistentFile(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Don't create the file + hasContent, err := hasUnreleasedContent() + if err == nil { + t.Error("Expected hasUnreleasedContent() to return an error for nonexistent file") + } + + if hasContent { + t.Error("Expected hasUnreleasedContent() to return false for nonexistent file") + } +} + +func TestSafeFileOperation_Success(t *testing.T) { + // Create a temporary directory for testing + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + + // Create initial file + initialContent := "initial content" + err := os.WriteFile(testFile, []byte(initialContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Perform safe operation that succeeds + newContent := "new content" + err = safeFileOperation(testFile, func() error { + return os.WriteFile(testFile, []byte(newContent), 0o644) + }) + + if err != nil { + t.Fatalf("safeFileOperation() failed: %v", err) + } + + // Verify the file has new content + content, err := os.ReadFile(testFile) + if err != nil { + t.Fatalf("Failed to read file after operation: %v", err) + } + + if string(content) != newContent { + t.Errorf("Expected file content '%s', got '%s'", newContent, string(content)) + } + + // Verify backup file was cleaned up + backupFile := testFile + ".backup" + if _, err := os.Stat(backupFile); !os.IsNotExist(err) { + t.Error("Backup file was not cleaned up after successful operation") + } +} + +func TestSafeFileOperation_Failure(t *testing.T) { + // Create a temporary directory for testing + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + + // Create initial file + initialContent := "initial content" + err := os.WriteFile(testFile, []byte(initialContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Perform safe operation that fails + err = safeFileOperation(testFile, func() error { + // First write something to the file + os.WriteFile(testFile, []byte("corrupted content"), 0o644) + // Then return an error to simulate failure + return os.ErrInvalid + }) + + if err == nil { + t.Error("Expected safeFileOperation() to return error") + } + + // Verify the file was restored to original content + content, err := os.ReadFile(testFile) + if err != nil { + t.Fatalf("Failed to read file after failed operation: %v", err) + } + + if string(content) != initialContent { + t.Errorf("Expected file content to be restored to '%s', got '%s'", initialContent, string(content)) + } + + // Verify backup file was cleaned up + backupFile := testFile + ".backup" + if _, err := os.Stat(backupFile); !os.IsNotExist(err) { + t.Error("Backup file was not cleaned up after failed operation") + } +} + +func TestSafeFileOperation_NewFile(t *testing.T) { + // Create a temporary directory for testing + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "newfile.txt") + + // Perform safe operation on non-existent file + content := "new file content" + err := safeFileOperation(testFile, func() error { + return os.WriteFile(testFile, []byte(content), 0o644) + }) + + if err != nil { + t.Fatalf("safeFileOperation() failed: %v", err) + } + + // Verify the file was created with correct content + fileContent, err := os.ReadFile(testFile) + if err != nil { + t.Fatalf("Failed to read created file: %v", err) + } + + if string(fileContent) != content { + t.Errorf("Expected file content '%s', got '%s'", content, string(fileContent)) + } +} + +func TestCopyFile(t *testing.T) { + // Create a temporary directory for testing + tmpDir := t.TempDir() + srcFile := filepath.Join(tmpDir, "source.txt") + dstFile := filepath.Join(tmpDir, "destination.txt") + + // Create source file + content := "test content for copying" + err := os.WriteFile(srcFile, []byte(content), 0o644) + if err != nil { + t.Fatalf("Failed to create source file: %v", err) + } + + // Copy the file + err = copyFile(srcFile, dstFile) + if err != nil { + t.Fatalf("copyFile() failed: %v", err) + } + + // Verify destination file exists and has correct content + dstContent, err := os.ReadFile(dstFile) + if err != nil { + t.Fatalf("Failed to read destination file: %v", err) + } + + if string(dstContent) != content { + t.Errorf("Expected destination content '%s', got '%s'", content, string(dstContent)) + } +} + +func TestCopyFile_NonexistentSource(t *testing.T) { + // Create a temporary directory for testing + tmpDir := t.TempDir() + srcFile := filepath.Join(tmpDir, "nonexistent.txt") + dstFile := filepath.Join(tmpDir, "destination.txt") + + // Try to copy non-existent file + err := copyFile(srcFile, dstFile) + if err == nil { + t.Error("Expected copyFile() to return error for non-existent source") + } + + // Verify destination file was not created + if _, err := os.Stat(dstFile); !os.IsNotExist(err) { + t.Error("Destination file should not exist after failed copy") + } +} + +func TestUpdateVersion(t *testing.T) { + tests := []struct { + name string + currentVersion string + expectedVersion string + }{ + { + name: "Alpha version increment", + currentVersion: "v3.0.0-alpha.12", + expectedVersion: "v3.0.0-alpha.13", + }, + { + name: "Beta version increment", + currentVersion: "v3.0.0-beta.5", + expectedVersion: "v3.0.0-beta.6", + }, + { + name: "RC version increment", + currentVersion: "v2.5.0-rc.1", + expectedVersion: "v2.5.0-rc.2", + }, + { + name: "Patch version increment", + currentVersion: "v3.0.0", + expectedVersion: "v3.0.1", + }, + { + name: "Patch version with higher number", + currentVersion: "v1.2.15", + expectedVersion: "v1.2.16", + }, + { + name: "Pre-release without number becomes patch", + currentVersion: "v3.0.0-alpha", + expectedVersion: "v3.0.1", + }, + { + name: "Version without v prefix", + currentVersion: "3.0.0", + expectedVersion: "v3.0.1", + }, + { + name: "Alpha with large number", + currentVersion: "v3.0.0-alpha.999", + expectedVersion: "v3.0.0-alpha.1000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary directory for this test + tmpDir := t.TempDir() + tempVersionFile := filepath.Join(tmpDir, "version.txt") + + // Save original versionFile path + originalVersionFile := versionFile + defer func() { + // Restore original value + _ = originalVersionFile + }() + + // Write the current version to temp file + err := os.WriteFile(tempVersionFile, []byte(tt.currentVersion), 0o644) + if err != nil { + t.Fatalf("Failed to write test version file: %v", err) + } + + // Test the updateVersion function logic directly + result := func() string { + currentVersionData, err := os.ReadFile(tempVersionFile) + if err != nil { + t.Fatalf("Failed to read version file: %v", err) + } + currentVersion := strings.TrimSpace(string(currentVersionData)) + + // Check if it has a pre-release suffix (e.g., -alpha.12, -beta.1) + if strings.Contains(currentVersion, "-") { + // Split on the dash to separate version and pre-release + parts := strings.SplitN(currentVersion, "-", 2) + baseVersion := parts[0] + preRelease := parts[1] + + // Check if pre-release has a numeric suffix (e.g., alpha.12) + lastDotIndex := strings.LastIndex(preRelease, ".") + if lastDotIndex != -1 { + preReleaseTag := preRelease[:lastDotIndex] + numberStr := preRelease[lastDotIndex+1:] + + // Try to parse the number + if number, err := strconv.Atoi(numberStr); err == nil { + // Increment the pre-release number + number++ + newVersion := fmt.Sprintf("%s-%s.%d", baseVersion, preReleaseTag, number) + return newVersion + } + } + + // If we can't parse the pre-release format, just increment patch version + // and remove pre-release suffix + return testIncrementPatchVersion(baseVersion) + } + + // No pre-release suffix, just increment patch version + return testIncrementPatchVersion(currentVersion) + }() + + if result != tt.expectedVersion { + t.Errorf("updateVersion() = %v, want %v", result, tt.expectedVersion) + } + }) + } +} + +// testIncrementPatchVersion is a test version of incrementPatchVersion that doesn't write to file +func testIncrementPatchVersion(version string) string { + // Remove 'v' prefix if present + versionWithoutV := strings.TrimPrefix(version, "v") + + // Split into major.minor.patch + parts := strings.Split(versionWithoutV, ".") + if len(parts) != 3 { + // Not a valid semver, return as-is + return version + } + + // Parse patch version + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return version + } + + // Increment patch + patch++ + + // Reconstruct version + return fmt.Sprintf("v%s.%s.%d", parts[0], parts[1], patch) +} + +// extractTestContent is a test helper that extracts changelog content using the same logic as extractChangelogContent +func extractTestContent(contentStr string) string { + lines := strings.Split(contentStr, "\n") + + var result []string + var inExampleSection bool + var inCommentBlock bool + var hasActualContent bool + var currentSection string + + for i, line := range lines { + trimmedLine := strings.TrimSpace(line) + + // Track comment blocks (handle multi-line comments) + if strings.Contains(line, "") { + inCommentBlock = false + } + continue + } + if inCommentBlock { + if strings.Contains(line, "-->") { + inCommentBlock = false + } + continue + } + + // Skip the main title + if strings.HasPrefix(trimmedLine, "# Unreleased Changes") { + continue + } + + // Check if we're entering the example section + if strings.HasPrefix(trimmedLine, "---") || strings.HasPrefix(trimmedLine, "### Example Entries") { + inExampleSection = true + continue + } + + // Skip example section content + if inExampleSection { + continue + } + + // Handle section headers + if strings.HasPrefix(trimmedLine, "##") { + currentSection = trimmedLine + // Only include section headers that have content after them + // We'll add it later if we find content + continue + } + + // Handle bullet points + if strings.HasPrefix(trimmedLine, "-") || strings.HasPrefix(trimmedLine, "*") { + // Check if this is actual content (not empty) + content := strings.TrimSpace(trimmedLine[1:]) + if content != "" { + // If this is the first content in a section, add the section header first + if currentSection != "" { + // Only add empty line if this isn't the first section + if len(result) > 0 { + result = append(result, "") + } + result = append(result, currentSection) + currentSection = "" // Reset so we don't add it again + } + result = append(result, line) + hasActualContent = true + } + } else if trimmedLine != "" && !strings.HasPrefix(trimmedLine, " + +## Fixed + + +--- + +### Example Entries: + +**Added:** +- Add support for custom window icons in application options +- Add new SetWindowIcon() method to runtime API (#1234)` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + + if hasContent { + t.Error("Expected hasUnreleasedContent() to return false when content is only in example section") + } +} + +func TestHasUnreleasedContent_WithMixedContent(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a file with both real content and example content + testContent := `# Unreleased Changes + +## Added +- Real feature addition here + +## Fixed + + +--- + +### Example Entries: + +**Added:** +- Add support for custom window icons in application options` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + + if !hasContent { + t.Error("Expected hasUnreleasedContent() to return true when file has real content") + } +} + +// Integration test for the complete cleanup workflow +func TestCleanupWorkflow_Integration(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a changelog file with actual content + testContent := `# Unreleased Changes + +## Added +- Add comprehensive changelog processing system +- Add validation for Keep a Changelog format compliance + +## Fixed +- Fix parsing issues with various markdown bullet styles +- Fix validation edge cases for empty content sections` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Step 1: Check that file has content + hasContent, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed: %v", err) + } + if !hasContent { + t.Fatal("Expected file to have content") + } + + // Step 2: Perform safe cleanup operation + err = safeFileOperation(unreleasedChangelogFile, func() error { + return clearUnreleasedChangelog() + }) + if err != nil { + t.Fatalf("Safe cleanup operation failed: %v", err) + } + + // Step 3: Verify file was reset to template + content, err := os.ReadFile(unreleasedChangelogFile) + if err != nil { + t.Fatalf("Failed to read file after cleanup: %v", err) + } + + template := getUnreleasedChangelogTemplate() + if string(content) != template { + t.Error("File was not properly reset to template") + } + + // Step 4: Verify original content is gone + if strings.Contains(string(content), "Add comprehensive changelog processing system") { + t.Error("Original content still present after cleanup") + } + + // Step 5: Verify file no longer has content + hasContentAfter, err := hasUnreleasedContent() + if err != nil { + t.Fatalf("hasUnreleasedContent() failed after cleanup: %v", err) + } + if hasContentAfter { + t.Error("File should not have content after cleanup") + } +} + +func TestFullReleaseWorkflow_OnlyNonEmptySections(t *testing.T) { + cleanup, projectRoot := setupTestEnvironment(t) + defer cleanup() + + // Create subdirectories to match expected structure + err := os.MkdirAll(filepath.Join(projectRoot, "v3", "internal", "version"), 0755) + if err != nil { + t.Fatalf("Failed to create version directory: %v", err) + } + + err = os.MkdirAll(filepath.Join(projectRoot, "docs", "src", "content", "docs"), 0755) + if err != nil { + t.Fatalf("Failed to create docs directory: %v", err) + } + + // Create version file + versionFile := filepath.Join(projectRoot, "v3", "internal", "version", "version.txt") + err = os.WriteFile(versionFile, []byte("v1.0.0-alpha.5"), 0644) + if err != nil { + t.Fatalf("Failed to create version file: %v", err) + } + + // Create initial changelog + changelogFile := filepath.Join(projectRoot, "docs", "src", "content", "docs", "changelog.mdx") + initialChangelog := `--- +title: Changelog +--- + +## [Unreleased] + +## v1.0.0-alpha.4 - 2024-01-01 + +### Added +- Previous feature + +` + err = os.WriteFile(changelogFile, []byte(initialChangelog), 0644) + if err != nil { + t.Fatalf("Failed to create changelog file: %v", err) + } + + // Create UNRELEASED_CHANGELOG.md with mixed content + unreleasedContent := `# Unreleased Changes + +## Added +- New amazing feature +- Another cool addition + +## Changed + + +## Fixed + + +## Deprecated +- Old API method + +## Removed + + +## Security + +` + // The script expects the file at ../../UNRELEASED_CHANGELOG.md relative to release dir + unreleasedFile := filepath.Join(projectRoot, "v3", "UNRELEASED_CHANGELOG.md") + err = os.WriteFile(unreleasedFile, []byte(unreleasedContent), 0644) + if err != nil { + t.Fatalf("Failed to create unreleased changelog: %v", err) + } + + // Run the release process simulation + // Read and process the files manually since we can't override constants + content, err := os.ReadFile(unreleasedFile) + if err != nil { + t.Fatalf("Failed to read unreleased file: %v", err) + } + + // Extract content using the same logic as extractChangelogContent + changelogContent := extractTestContent(string(content)) + if changelogContent == "" { + t.Fatal("Failed to extract any content") + } + + // Verify only non-empty sections were extracted + if !strings.Contains(changelogContent, "## Added") { + t.Error("Expected '## Added' section to be included") + } + if !strings.Contains(changelogContent, "## Deprecated") { + t.Error("Expected '## Deprecated' section to be included") + } + + // Verify empty sections were NOT extracted + emptySections := []string{"## Changed", "## Fixed", "## Removed", "## Security"} + for _, section := range emptySections { + if strings.Contains(changelogContent, section) { + t.Errorf("Expected empty section '%s' to NOT be included", section) + } + } + + // Simulate updating the main changelog + changelogData, _ := os.ReadFile(changelogFile) + changelog := string(changelogData) + changelogSplit := strings.Split(changelog, "## [Unreleased]") + + newVersion := "v1.0.0-alpha.6" + today := "2024-01-15" + newChangelog := changelogSplit[0] + "## [Unreleased]\n\n## " + newVersion + " - " + today + "\n\n" + changelogContent + changelogSplit[1] + + // Verify the final changelog format + if !strings.Contains(newChangelog, "## v1.0.0-alpha.6 - 2024-01-15") { + t.Error("Expected new version header in changelog") + } + + // Count occurrences of section headers in the new version section + newVersionSection := strings.Split(newChangelog, "## v1.0.0-alpha.4")[0] + + addedCount := strings.Count(newVersionSection, "## Added") + if addedCount != 1 { + t.Errorf("Expected exactly 1 '## Added' section, got %d", addedCount) + } + + deprecatedCount := strings.Count(newVersionSection, "## Deprecated") + if deprecatedCount != 1 { + t.Errorf("Expected exactly 1 '## Deprecated' section, got %d", deprecatedCount) + } + + // Ensure no empty sections in the new version section + for _, section := range []string{"## Changed", "## Fixed", "## Removed", "## Security"} { + count := strings.Count(newVersionSection, section) + if count > 0 { + t.Errorf("Expected 0 occurrences of empty section '%s', got %d", section, count) + } + } +} + +// Test error handling during cleanup +func TestCleanupWorkflow_ErrorHandling(t *testing.T) { + cleanup, _ := setupTestEnvironment(t) + defer cleanup() + + // Create a changelog file with content + testContent := `# Unreleased Changes + +## Added +- Test content that should be preserved on error` + + err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644) + if err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // Simulate an error during cleanup by making the operation fail + err = safeFileOperation(unreleasedChangelogFile, func() error { + // First corrupt the file + os.WriteFile(unreleasedChangelogFile, []byte("corrupted"), 0o644) + // Then return an error + return os.ErrPermission + }) + + if err == nil { + t.Error("Expected safeFileOperation to return error") + } + + // Verify original content was restored + content, err := os.ReadFile(unreleasedChangelogFile) + if err != nil { + t.Fatalf("Failed to read file after error: %v", err) + } + + if string(content) != testContent { + t.Error("Original content was not restored after error") + } +} diff --git a/v3/tasks/release/test_create_release_notes.go b/v3/tasks/release/test_create_release_notes.go new file mode 100644 index 000000000..a9ac66d25 --- /dev/null +++ b/v3/tasks/release/test_create_release_notes.go @@ -0,0 +1,172 @@ +// +build ignore + +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + fmt.Println("Testing release.go --create-release-notes functionality") + fmt.Println("=" + strings.Repeat("=", 50)) + + // Test cases + testCases := []struct { + name string + content string + expected string + shouldFail bool + }{ + { + name: "Valid changelog with content", + content: `# Unreleased Changes + + + +## Added +- Add Windows dark theme support for menus +- Add new --create-release-notes flag + +## Changed +- Update Go version to 1.23 +- Improve error handling + +## Fixed +- Fix nightly release workflow +- Fix changelog extraction + +--- + +### Example Entries: +Example content here`, + expected: `## Added +- Add Windows dark theme support for menus +- Add new --create-release-notes flag + +## Changed +- Update Go version to 1.23 +- Improve error handling + +## Fixed +- Fix nightly release workflow +- Fix changelog extraction`, + shouldFail: false, + }, + { + name: "Empty changelog", + content: `# Unreleased Changes + +## Added + + +## Changed + + +## Fixed + + +---`, + expected: "", + shouldFail: true, + }, + { + name: "Only one section with content", + content: `# Unreleased Changes + +## Added + + +## Changed +- Single change item here + +## Fixed + + +---`, + expected: `## Changed +- Single change item here`, + shouldFail: false, + }, + } + + // Create a temporary directory for testing + tmpDir, err := os.MkdirTemp("", "release-test-*") + if err != nil { + fmt.Printf("Failed to create temp dir: %v\n", err) + os.Exit(1) + } + defer os.RemoveAll(tmpDir) + + // Save current directory + originalDir, _ := os.Getwd() + + for i, tc := range testCases { + fmt.Printf("\nTest %d: %s\n", i+1, tc.name) + fmt.Println("-" + strings.Repeat("-", 40)) + + // Create test changelog + changelogPath := filepath.Join(tmpDir, "UNRELEASED_CHANGELOG.md") + err = os.WriteFile(changelogPath, []byte(tc.content), 0644) + if err != nil { + fmt.Printf("โŒ Failed to write test changelog: %v\n", err) + continue + } + + // Create release notes path + releaseNotesPath := filepath.Join(tmpDir, fmt.Sprintf("release_notes_%d.md", i)) + + // Change to temp dir (so relative paths work) + os.Chdir(tmpDir) + + // Run the command + cmd := exec.Command("go", "run", filepath.Join(originalDir, "release.go"), "--create-release-notes", releaseNotesPath) + output, err := cmd.CombinedOutput() + + // Change back + os.Chdir(originalDir) + + if tc.shouldFail { + if err == nil { + fmt.Printf("โŒ Expected failure but command succeeded\n") + fmt.Printf("Output: %s\n", output) + } else { + fmt.Printf("โœ… Failed as expected: %v\n", err) + } + } else { + if err != nil { + fmt.Printf("โŒ Command failed: %v\n", err) + fmt.Printf("Output: %s\n", output) + } else { + fmt.Printf("โœ… Command succeeded\n") + + // Read and verify the output + content, err := os.ReadFile(releaseNotesPath) + if err != nil { + fmt.Printf("โŒ Failed to read release notes: %v\n", err) + } else { + actualContent := strings.TrimSpace(string(content)) + expectedContent := strings.TrimSpace(tc.expected) + + if actualContent == expectedContent { + fmt.Printf("โœ… Content matches expected\n") + } else { + fmt.Printf("โŒ Content mismatch\n") + fmt.Printf("Expected:\n%s\n", expectedContent) + fmt.Printf("Actual:\n%s\n", actualContent) + } + } + } + } + + // Clean up + os.Remove(changelogPath) + os.Remove(releaseNotesPath) + } + + fmt.Println("\n" + "=" + strings.Repeat("=", 50)) + fmt.Println("Testing complete!") +} \ No newline at end of file diff --git a/v3/tasks/release/test_edge_cases.bat b/v3/tasks/release/test_edge_cases.bat new file mode 100644 index 000000000..390a972ab --- /dev/null +++ b/v3/tasks/release/test_edge_cases.bat @@ -0,0 +1,111 @@ +@echo off +echo Testing edge cases for release.go +echo ================================= + +set ORIGINAL_DIR=%CD% +cd ..\.. + +REM Backup existing file +if exist UNRELEASED_CHANGELOG.md ( + copy UNRELEASED_CHANGELOG.md UNRELEASED_CHANGELOG.md.backup > nul +) + +echo. +echo Test 1: Empty changelog (should fail) +echo ------------------------------------- + +REM Create empty changelog +echo # Unreleased Changes > UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Added >> UNRELEASED_CHANGELOG.md +echo ^<^!-- New features --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Changed >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Changes --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Fixed >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Bug fixes --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo --- >> UNRELEASED_CHANGELOG.md + +cd tasks\release +go run release.go --create-release-notes 2>&1 +if %ERRORLEVEL% NEQ 0 ( + echo SUCCESS: Command failed as expected for empty changelog +) else ( + echo FAIL: Command should have failed for empty changelog +) + +echo. +echo Test 2: Only comments (should fail) +echo ----------------------------------- + +cd ..\.. +echo # Unreleased Changes > UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Added >> UNRELEASED_CHANGELOG.md +echo ^<^!-- This is just a comment --^> >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Another comment --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo --- >> UNRELEASED_CHANGELOG.md + +cd tasks\release +go run release.go --create-release-notes 2>&1 +if %ERRORLEVEL% NEQ 0 ( + echo SUCCESS: Command failed as expected for comment-only changelog +) else ( + echo FAIL: Command should have failed for comment-only changelog +) + +echo. +echo Test 3: Mixed bullet styles +echo --------------------------- + +cd ..\.. +echo # Unreleased Changes > UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Added >> UNRELEASED_CHANGELOG.md +echo - Dash bullet point >> UNRELEASED_CHANGELOG.md +echo * Asterisk bullet point >> UNRELEASED_CHANGELOG.md +echo - Another dash >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo --- >> UNRELEASED_CHANGELOG.md + +cd tasks\release +go run release.go --create-release-notes +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: Mixed bullet styles handled + echo Content: + type ..\..\release_notes.md +) else ( + echo FAIL: Mixed bullet styles should work +) + +echo. +echo Test 4: Custom output path +echo -------------------------- + +go run release.go --create-release-notes ..\..\custom_notes.md +if %ERRORLEVEL% EQU 0 ( + if exist "..\..\custom_notes.md" ( + echo SUCCESS: Custom path works + del ..\..\custom_notes.md + ) else ( + echo FAIL: Custom path file not created + ) +) else ( + echo FAIL: Custom path should work +) + +REM Clean up +cd ..\.. +if exist release_notes.md del release_notes.md +if exist UNRELEASED_CHANGELOG.md.backup ( + move /Y UNRELEASED_CHANGELOG.md.backup UNRELEASED_CHANGELOG.md > nul +) + +cd %ORIGINAL_DIR% + +echo. +echo ================================= +echo Edge case testing complete! \ No newline at end of file diff --git a/v3/tasks/release/test_simple.bat b/v3/tasks/release/test_simple.bat new file mode 100644 index 000000000..149655c0f --- /dev/null +++ b/v3/tasks/release/test_simple.bat @@ -0,0 +1,115 @@ +@echo off +echo Testing release.go --create-release-notes functionality +echo ====================================================== + +REM Save current directory +set ORIGINAL_DIR=%CD% + +REM Go to v3 root (where UNRELEASED_CHANGELOG.md should be) +cd ..\.. + +REM Backup existing UNRELEASED_CHANGELOG.md if it exists +if exist UNRELEASED_CHANGELOG.md ( + copy UNRELEASED_CHANGELOG.md UNRELEASED_CHANGELOG.md.backup > nul +) + +REM Create a test UNRELEASED_CHANGELOG.md +echo # Unreleased Changes > UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ^<^!-- >> UNRELEASED_CHANGELOG.md +echo This file is used to collect changelog entries for the next v3-alpha release. >> UNRELEASED_CHANGELOG.md +echo --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Added >> UNRELEASED_CHANGELOG.md +echo ^<^!-- New features, capabilities, or enhancements --^> >> UNRELEASED_CHANGELOG.md +echo - Add Windows dark theme support for menus and menubar >> UNRELEASED_CHANGELOG.md +echo - Add `--create-release-notes` flag to release script >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Changed >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Changes in existing functionality --^> >> UNRELEASED_CHANGELOG.md +echo - Update Go version to 1.23 in workflow >> UNRELEASED_CHANGELOG.md +echo - Improve error handling in release process >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Fixed >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Bug fixes --^> >> UNRELEASED_CHANGELOG.md +echo - Fix nightly release workflow changelog extraction >> UNRELEASED_CHANGELOG.md +echo - Fix Go cache configuration in GitHub Actions >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Deprecated >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Soon-to-be removed features --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Removed >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Features removed in this release --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ## Security >> UNRELEASED_CHANGELOG.md +echo ^<^!-- Security-related changes --^> >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo --- >> UNRELEASED_CHANGELOG.md +echo. >> UNRELEASED_CHANGELOG.md +echo ### Example Entries: >> UNRELEASED_CHANGELOG.md + +echo. +echo Test 1: Running with valid content +echo ----------------------------------- + +REM Run the release script +cd tasks\release +go run release.go --create-release-notes + +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: Command succeeded + + REM Check if release_notes.md was created + if exist "..\..\release_notes.md" ( + echo SUCCESS: release_notes.md was created + echo. + echo Content: + echo -------- + type ..\..\release_notes.md + echo. + echo -------- + ) else ( + echo FAIL: release_notes.md was NOT created + ) +) else ( + echo FAIL: Command failed +) + +echo. +echo Test 2: Check --check-only flag +echo -------------------------------- + +REM Test the check-only flag +go run release.go --check-only +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: --check-only detected content +) else ( + echo FAIL: --check-only did not detect content +) + +echo. +echo Test 3: Check --extract-changelog flag +echo -------------------------------------- + +REM Test the extract-changelog flag +go run release.go --extract-changelog +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: --extract-changelog succeeded +) else ( + echo FAIL: --extract-changelog failed +) + +REM Clean up +cd ..\.. +if exist release_notes.md del release_notes.md + +REM Restore original UNRELEASED_CHANGELOG.md if it exists +if exist UNRELEASED_CHANGELOG.md.backup ( + move /Y UNRELEASED_CHANGELOG.md.backup UNRELEASED_CHANGELOG.md > nul +) + +cd %ORIGINAL_DIR% + +echo. +echo ====================================================== +echo Testing complete! \ No newline at end of file diff --git a/v3/tasks/release/test_simple.sh b/v3/tasks/release/test_simple.sh new file mode 100644 index 000000000..a04446367 --- /dev/null +++ b/v3/tasks/release/test_simple.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +echo "Testing release.go --create-release-notes functionality" +echo "======================================================" + +# Save current directory +ORIGINAL_DIR=$(pwd) + +# Go to v3 root (where UNRELEASED_CHANGELOG.md should be) +cd ../.. + +# Create a test UNRELEASED_CHANGELOG.md +cat > UNRELEASED_CHANGELOG.md << 'EOF' +# Unreleased Changes + + + +## Added + +- Add Windows dark theme support for menus and menubar +- Add `--create-release-notes` flag to release script + +## Changed + +- Update Go version to 1.23 in workflow +- Improve error handling in release process + +## Fixed + +- Fix nightly release workflow changelog extraction +- Fix Go cache configuration in GitHub Actions + +## Deprecated + + +## Removed + + +## Security + + +--- + +### Example Entries: + +**Added:** +- Example content +EOF + +echo "" +echo "Test 1: Running with valid content" +echo "-----------------------------------" + +# Run the release script +cd tasks/release +if go run release.go --create-release-notes; then + echo "โœ… Command succeeded" + + # Check if release_notes.md was created + if [ -f "../../release_notes.md" ]; then + echo "โœ… release_notes.md was created" + echo "" + echo "Content:" + echo "--------" + cat ../../release_notes.md + echo "" + echo "--------" + else + echo "โŒ release_notes.md was NOT created" + fi +else + echo "โŒ Command failed" +fi + +echo "" +echo "Test 2: Check --check-only flag" +echo "--------------------------------" + +# Test the check-only flag +if go run release.go --check-only; then + echo "โœ… --check-only detected content" +else + echo "โŒ --check-only did not detect content" +fi + +echo "" +echo "Test 3: Check --extract-changelog flag" +echo "--------------------------------------" + +# Test the extract-changelog flag +OUTPUT=$(go run release.go --extract-changelog 2>&1) +if [ $? -eq 0 ]; then + echo "โœ… --extract-changelog succeeded" + echo "Output:" + echo "-------" + echo "$OUTPUT" + echo "-------" +else + echo "โŒ --extract-changelog failed" + echo "Error: $OUTPUT" +fi + +# Clean up +cd ../.. +rm -f release_notes.md + +# Restore original UNRELEASED_CHANGELOG.md if it exists +if [ -f "UNRELEASED_CHANGELOG.md.backup" ]; then + mv UNRELEASED_CHANGELOG.md.backup UNRELEASED_CHANGELOG.md +fi + +cd "$ORIGINAL_DIR" + +echo "" +echo "======================================================" +echo "Testing complete!" \ No newline at end of file diff --git a/v3/tasks/sed/sed.go b/v3/tasks/sed/sed.go new file mode 100644 index 000000000..12e55f8d3 --- /dev/null +++ b/v3/tasks/sed/sed.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/leaanthony/clir" + + "github.com/samber/lo" +) + +func main() { + app := clir.NewCli("sed", "A simple sed replacement", "v1") + app.NewSubCommandFunction("replace", "Replace a string in files", ReplaceInFiles) + err := app.Run() + if err != nil { + println(err.Error()) + os.Exit(1) + } +} + +type ReplaceInFilesOptions struct { + Dir string `name:"dir" help:"Directory to search in"` + OldString string `name:"old" description:"The string to replace"` + NewString string `name:"new" description:"The string to replace with"` + Extensions string `name:"ext" description:"The file extensions to process"` + Ignore string `name:"ignore" description:"The files to ignore"` +} + +func ReplaceInFiles(options *ReplaceInFilesOptions) error { + extensions := strings.Split(options.Extensions, ",") + ignore := strings.Split(options.Ignore, ",") + err := filepath.Walk(options.Dir, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + ext := filepath.Ext(path) + if !lo.Contains(extensions, ext) { + println("Skipping", path) + return nil + } + filename := filepath.Base(path) + if lo.Contains(ignore, filename) { + println("Ignoring:", path) + return nil + } + + println("Processing file:", path) + + content, err := os.ReadFile(path) + if err != nil { + return err + } + + newContent := strings.Replace(string(content), options.OldString, options.NewString, -1) + + return os.WriteFile(path, []byte(newContent), info.Mode()) + }) + + if err != nil { + return fmt.Errorf("Error while replacing in files: %v", err) + } + + return nil +} diff --git a/v3/test/docker/Dockerfile.linux-arm64 b/v3/test/docker/Dockerfile.linux-arm64 new file mode 100644 index 000000000..fb6e4054b --- /dev/null +++ b/v3/test/docker/Dockerfile.linux-arm64 @@ -0,0 +1,185 @@ +# Build Linux binaries for Wails v3 examples using Ubuntu 24.04 (ARM64 native) +FROM ubuntu:24.04 + +# Avoid interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies for Wails v3 (ARM64 native) +RUN apt-get update && apt-get install -y \ + --no-install-recommends \ + # Core build tools for ARM64 + build-essential \ + gcc \ + g++ \ + pkg-config \ + # GTK and WebKit dependencies for Ubuntu 24.04 + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + # Additional dependencies that might be needed + libayatana-appindicator3-dev \ + # Git for go mod operations + git \ + # CA certificates for HTTPS + ca-certificates \ + # wget for downloading Go + wget \ + # Clean up apt cache and lists + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Install Go 1.24 ARM64 version +ENV GO_VERSION=1.24.0 +RUN cd /tmp && \ + wget https://go.dev/dl/go${GO_VERSION}.linux-arm64.tar.gz && \ + wget https://go.dev/dl/go${GO_VERSION}.linux-arm64.tar.gz.sha256 && \ + echo "$(cat go${GO_VERSION}.linux-arm64.tar.gz.sha256) go${GO_VERSION}.linux-arm64.tar.gz" | sha256sum -c - && \ + tar -C /usr/local -xzf go${GO_VERSION}.linux-arm64.tar.gz && \ + rm go${GO_VERSION}.linux-arm64.tar.gz go${GO_VERSION}.linux-arm64.tar.gz.sha256 + +# Set Go environment for ARM64 native compilation +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOPATH="/go" +ENV PATH="${GOPATH}/bin:${PATH}" +ENV CGO_ENABLED=1 +ENV GOOS=linux +ENV GOARCH=arm64 + +# Set working directory +WORKDIR /build + +# Copy the entire v3 directory structure +COPY . /build/ + +# Create build script for ARM64 native compilation +# hadolint ignore=DL1000 +RUN cat > /build/build-linux-arm64.sh << 'EOF' +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "๐Ÿง Building Wails v3 examples for Linux ARM64 (Ubuntu 24.04)" +echo "Go version: $(go version)" +echo "Architecture: $(uname -m)" +echo "Build directory: $(pwd)" +echo "" + +# Function to build a single example +build_example() { + local example_name="$1" + local example_dir="/build/examples/$example_name" + + if [ ! -d "$example_dir" ]; then + echo -e "${RED}โœ—${NC} Example directory $example_name not found" + return 1 + fi + + if [ ! -f "$example_dir/main.go" ]; then + echo -e "${YELLOW}โš ${NC} Skipping $example_name (no main.go)" + return 0 + fi + + echo -e "Building ${YELLOW}$example_name${NC} for ARM64..." + cd "$example_dir" + + # Update go.mod for Docker environment + if [ -f go.mod ]; then + go mod edit -dropreplace github.com/wailsapp/wails/v3 2>/dev/null || true + go mod edit -replace github.com/wailsapp/wails/v3=/build + fi + + # Tidy dependencies + echo " Running go mod tidy..." + if ! go mod tidy; then + echo -e "${RED}โœ—${NC} go mod tidy failed for $example_name" + return 1 + fi + + # Build the example for ARM64 + echo " Compiling for ARM64..." + if go build -o "testbuild-$example_name-linux-arm64"; then + echo -e "${GREEN}โœ“${NC} Successfully built $example_name for ARM64" + ls -la "testbuild-$example_name-linux-arm64" + return 0 + else + echo -e "${RED}โœ—${NC} Build failed for $example_name" + return 1 + fi +} + +# Main execution +if [ $# -eq 0 ]; then + # Build all examples + echo "Building all examples for ARM64..." + cd /build/examples + + failed_examples=() + successful_examples=() + skipped_examples=() + + for example_dir in */; do + example_name=$(basename "$example_dir") + + if build_example "$example_name"; then + if [ -f "/build/examples/$example_name/testbuild-$example_name-linux-arm64" ]; then + successful_examples+=("$example_name") + else + skipped_examples+=("$example_name") + fi + else + failed_examples+=("$example_name") + fi + + echo "" + done + + echo "==============================" + echo "๐Ÿ—๏ธ ARM64 BUILD SUMMARY" + echo "==============================" + echo -e "${GREEN}โœ“ Successful builds (${#successful_examples[@]}):${NC}" + for example in "${successful_examples[@]}"; do + echo " $example" + done + + if [ ${#skipped_examples[@]} -gt 0 ]; then + echo -e "${YELLOW}โš  Skipped (${#skipped_examples[@]}):${NC}" + for example in "${skipped_examples[@]}"; do + echo " $example (no main.go)" + done + fi + + if [ ${#failed_examples[@]} -gt 0 ]; then + echo -e "${RED}โœ— Failed builds (${#failed_examples[@]}):${NC}" + for example in "${failed_examples[@]}"; do + echo " $example" + done + fi + + echo "" + echo "Total: ${#successful_examples[@]} successful, ${#failed_examples[@]} failed, ${#skipped_examples[@]} skipped" + + if [ ${#failed_examples[@]} -eq 0 ]; then + echo -e "${GREEN}๐ŸŽ‰ All buildable examples compiled successfully for ARM64!${NC}" + exit 0 + else + echo -e "${RED}โŒ Some examples failed to build for ARM64${NC}" + exit 1 + fi + +else + # Build specific example + example_name="$1" + echo "Building specific example for ARM64: $example_name" + build_example "$example_name" +fi +EOF + +# Make the script executable +RUN chmod +x /build/build-linux-arm64.sh + +# Default command +CMD ["/build/build-linux-arm64.sh"] \ No newline at end of file diff --git a/v3/test/docker/Dockerfile.linux-x86_64 b/v3/test/docker/Dockerfile.linux-x86_64 new file mode 100644 index 000000000..b3a1dcf80 --- /dev/null +++ b/v3/test/docker/Dockerfile.linux-x86_64 @@ -0,0 +1,185 @@ +# Build Linux binaries for Wails v3 examples using Ubuntu 24.04 (x86_64 native) +FROM --platform=linux/amd64 ubuntu:24.04 + +# Avoid interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies for Wails v3 (x86_64 native) +RUN apt-get update && apt-get install -y \ + --no-install-recommends \ + # Core build tools for x86_64 + build-essential \ + gcc \ + g++ \ + pkg-config \ + # GTK and WebKit dependencies for Ubuntu 24.04 + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + # Additional dependencies that might be needed + libayatana-appindicator3-dev \ + # Git for go mod operations + git \ + # CA certificates for HTTPS + ca-certificates \ + # wget for downloading Go + wget \ + # Clean up apt cache and lists + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Install Go 1.24 x86_64 version +ENV GO_VERSION=1.24.0 +RUN cd /tmp && \ + wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && \ + wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz.sha256 && \ + echo "$(cat go${GO_VERSION}.linux-amd64.tar.gz.sha256) go${GO_VERSION}.linux-amd64.tar.gz" | sha256sum -c - && \ + tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz && \ + rm go${GO_VERSION}.linux-amd64.tar.gz go${GO_VERSION}.linux-amd64.tar.gz.sha256 + +# Set Go environment for x86_64 native compilation +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOPATH="/go" +ENV PATH="${GOPATH}/bin:${PATH}" +ENV CGO_ENABLED=1 +ENV GOOS=linux +ENV GOARCH=amd64 + +# Set working directory +WORKDIR /build + +# Copy the entire v3 directory structure +COPY . /build/ + +# Create build script for x86_64 native compilation +# hadolint ignore=DL1000 +RUN cat > /build/build-linux-x86_64.sh << 'EOF' +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "๐Ÿง Building Wails v3 examples for Linux x86_64 (Ubuntu 24.04)" +echo "Go version: $(go version)" +echo "Architecture: $(uname -m)" +echo "Build directory: $(pwd)" +echo "" + +# Function to build a single example +build_example() { + local example_name="$1" + local example_dir="/build/examples/$example_name" + + if [ ! -d "$example_dir" ]; then + echo -e "${RED}โœ—${NC} Example directory $example_name not found" + return 1 + fi + + if [ ! -f "$example_dir/main.go" ]; then + echo -e "${YELLOW}โš ${NC} Skipping $example_name (no main.go)" + return 0 + fi + + echo -e "Building ${YELLOW}$example_name${NC} for x86_64..." + cd "$example_dir" + + # Update go.mod for Docker environment + if [ -f go.mod ]; then + go mod edit -dropreplace github.com/wailsapp/wails/v3 2>/dev/null || true + go mod edit -replace github.com/wailsapp/wails/v3=/build + fi + + # Tidy dependencies + echo " Running go mod tidy..." + if ! go mod tidy; then + echo -e "${RED}โœ—${NC} go mod tidy failed for $example_name" + return 1 + fi + + # Build the example for x86_64 + echo " Compiling for x86_64..." + if go build -o "testbuild-$example_name-linux-x86_64"; then + echo -e "${GREEN}โœ“${NC} Successfully built $example_name for x86_64" + ls -la "testbuild-$example_name-linux-x86_64" + return 0 + else + echo -e "${RED}โœ—${NC} Build failed for $example_name" + return 1 + fi +} + +# Main execution +if [ $# -eq 0 ]; then + # Build all examples + echo "Building all examples for x86_64..." + cd /build/examples + + failed_examples=() + successful_examples=() + skipped_examples=() + + for example_dir in */; do + example_name=$(basename "$example_dir") + + if build_example "$example_name"; then + if [ -f "/build/examples/$example_name/testbuild-$example_name-linux-x86_64" ]; then + successful_examples+=("$example_name") + else + skipped_examples+=("$example_name") + fi + else + failed_examples+=("$example_name") + fi + + echo "" + done + + echo "==============================" + echo "๐Ÿ—๏ธ x86_64 BUILD SUMMARY" + echo "==============================" + echo -e "${GREEN}โœ“ Successful builds (${#successful_examples[@]}):${NC}" + for example in "${successful_examples[@]}"; do + echo " $example" + done + + if [ ${#skipped_examples[@]} -gt 0 ]; then + echo -e "${YELLOW}โš  Skipped (${#skipped_examples[@]}):${NC}" + for example in "${skipped_examples[@]}"; do + echo " $example (no main.go)" + done + fi + + if [ ${#failed_examples[@]} -gt 0 ]; then + echo -e "${RED}โœ— Failed builds (${#failed_examples[@]}):${NC}" + for example in "${failed_examples[@]}"; do + echo " $example" + done + fi + + echo "" + echo "Total: ${#successful_examples[@]} successful, ${#failed_examples[@]} failed, ${#skipped_examples[@]} skipped" + + if [ ${#failed_examples[@]} -eq 0 ]; then + echo -e "${GREEN}๐ŸŽ‰ All buildable examples compiled successfully for x86_64!${NC}" + exit 0 + else + echo -e "${RED}โŒ Some examples failed to build for x86_64${NC}" + exit 1 + fi + +else + # Build specific example + example_name="$1" + echo "Building specific example for x86_64: $example_name" + build_example "$example_name" +fi +EOF + +# Make the script executable +RUN chmod +x /build/build-linux-x86_64.sh + +# Default command +CMD ["/build/build-linux-x86_64.sh"] \ No newline at end of file diff --git a/v3/tests/window-visibility-test/.gitignore b/v3/tests/window-visibility-test/.gitignore new file mode 100644 index 000000000..dc734a8b8 --- /dev/null +++ b/v3/tests/window-visibility-test/.gitignore @@ -0,0 +1,19 @@ +# Compiled binary +window-visibility-test +window-visibility-test.exe + +# Build artifacts +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binaries +*.test + +# Output of the go coverage tool +*.out + +# Go workspace file +go.work \ No newline at end of file diff --git a/v3/tests/window-visibility-test/README.md b/v3/tests/window-visibility-test/README.md new file mode 100644 index 000000000..04b4ec49e --- /dev/null +++ b/v3/tests/window-visibility-test/README.md @@ -0,0 +1,146 @@ +# Window Visibility Test - Issue #2861 + +This example demonstrates and tests the fixes implemented for [Wails v3 Issue #2861](https://github.com/wailsapp/wails/issues/2861) regarding application windows not showing on Windows 10 Pro due to efficiency mode. + +## Problem Background + +On Windows systems, the "efficiency mode" feature could prevent Wails applications from displaying windows properly. This occurred because: + +1. **WebView2 NavigationCompleted events** could be delayed or missed in efficiency mode +2. **Window visibility was gated** behind WebView2 navigation completion +3. **No fallback mechanisms** existed for delayed or failed navigation events + +## Solution Implemented + +The fix implements a **robust cross-platform window visibility pattern**: + +### Windows Improvements +- โœ… **Decouple window container from WebView state** - Windows show immediately +- โœ… **3-second timeout fallback** - Shows WebView if navigation is delayed +- โœ… **Efficiency mode prevention** - Sets WebView2 `IsVisible=true` per Microsoft guidance +- โœ… **Enhanced state tracking** - Proper visibility state management + +### Cross-Platform Consistency +- โœ… **macOS** - Already robust, documented best practices +- โœ… **Linux** - Added missing show/hide methods for both CGO and purego builds + +## Test Scenarios + +This example provides comprehensive testing for: + +### 1. **Basic Window Tests** +- **Normal Window**: Standard window creation - should appear immediately +- **Delayed Content Window**: Simulates heavy content loading (like Vue.js apps) +- **Hidden โ†’ Show Test**: Tests delayed showing after initial creation + +### 2. **Stress Tests** +- **Multiple Windows**: Creates 3 windows simultaneously +- **Rapid Creation**: Creates windows in quick succession + +### 3. **Critical Issue #2861 Test** +- **Efficiency Mode Test**: Specifically designed to reproduce and verify the fix +- Tests window container vs content loading timing +- Includes heavy content simulation + +## How to Run + +```bash +cd /path/to/wails/v3/examples/window-visibility-test +wails dev +``` + +## Testing Instructions + +### What to Look For +1. **Immediate Window Appearance** - Windows should appear within 100ms of clicking buttons +2. **Progressive Loading** - Content may load progressively, but window container visible immediately +3. **No Efficiency Mode Issues** - Windows appear even if Task Manager shows "efficiency mode" +4. **Consistent Cross-Platform Behavior** - Similar behavior on Windows, macOS, and Linux + +### How to Test +1. **Note the current time** displayed in the app +2. **Click any test button** or use menu items +3. **Immediately observe** if a window appears (should be within 100ms) +4. **Wait for content** to load and check reported timing +5. **Try multiple tests** in sequence to test robustness +6. **Test both buttons and menu items** for comprehensive coverage + +### Expected Results +- โœ… Window containers appear immediately upon button click +- โœ… Content loads progressively within 2-3 seconds +- โœ… No blank or invisible windows, even under efficiency mode +- โœ… Activity log shows sub-100ms window creation times +- โœ… All test scenarios work consistently + +## Manual Testing Checklist + +### Windows 10 Pro (Primary Target) +- [ ] Test with efficiency mode enabled in Task Manager +- [ ] Create windows while system is under load +- [ ] Test rapid window creation scenarios +- [ ] Verify WebView2 content loads after container appears +- [ ] Check activity log for sub-100ms creation times + +### Windows 11 +- [ ] Verify consistent behavior with Windows 10 Pro fixes +- [ ] Test efficiency mode scenarios +- [ ] Validate timeout fallback mechanisms + +### macOS +- [ ] Confirm existing robust behavior maintained +- [ ] Test all window creation scenarios +- [ ] Verify no regressions introduced + +### Linux +- [ ] Test both CGO and purego builds +- [ ] Verify new show/hide methods work correctly +- [ ] Test window positioning and timing + +## Technical Implementation Details + +### Window Creation Flow +``` +1. User clicks button โ†’ JavaScript calls Go backend +2. Go creates WebviewWindow โ†’ Sets properties +3. Go calls window.Show() โ†’ IMMEDIATE window container display +4. WebView2 starts navigation โ†’ Progressive content loading +5. Timeout fallback ensures WebView shows even if navigation delayed +``` + +### Key Code Changes +- **Windows**: `/v3/pkg/application/webview_window_windows.go` +- **macOS**: `/v3/pkg/application/webview_window_darwin.go` +- **Linux**: `/v3/pkg/application/webview_window_linux.go`, `linux_cgo.go`, `linux_purego.go` + +## Reporting Test Results + +When testing, please report: + +1. **Platform & OS Version** (e.g., "Windows 10 Pro 21H2", "macOS 13.1", "Ubuntu 22.04") +2. **Window Creation Timing** (from activity log) +3. **Any Delayed or Missing Windows** +4. **Efficiency Mode Status** (Windows only - check Task Manager) +5. **Content Loading Behavior** (immediate container vs progressive content) +6. **Any Error Messages** in activity log or console + +### Sample Test Report Format +``` +Platform: Windows 10 Pro 21H2 +Efficiency Mode: Enabled +Results: +- Normal Window: โœ… Appeared immediately (<50ms) +- Delayed Content: โœ… Container immediate, content loaded in 2.1s +- Multiple Windows: โœ… All 3 appeared simultaneously +- Critical Test: โœ… Window appeared immediately, content progressive +Notes: No issues observed, all windows visible immediately +``` + +## Architecture Notes + +This example demonstrates the **preferred window visibility pattern** for web-based desktop applications: + +1. **Separate Concerns**: Window container vs web content readiness +2. **Immediate Feedback**: Users see window immediately +3. **Progressive Enhancement**: Content loads and appears when ready +4. **Robust Fallbacks**: Multiple strategies for edge cases +5. **Cross-Platform Consistency**: Same behavior on all platforms diff --git a/v3/tests/window-visibility-test/TESTING_GUIDE.md b/v3/tests/window-visibility-test/TESTING_GUIDE.md new file mode 100644 index 000000000..dbb3bfd04 --- /dev/null +++ b/v3/tests/window-visibility-test/TESTING_GUIDE.md @@ -0,0 +1,159 @@ +# Testing Guide - Window Visibility Issue #2861 + +## Quick Start + +1. **Build and run the application:** + ```bash + cd v3/examples/window-visibility-test + ./build.sh + # OR + wails dev + ``` + +2. **Main testing interface:** + - The app opens with a comprehensive testing dashboard + - Contains multiple test scenarios accessible via buttons + - Also provides menu-based testing (File, Tests, Help menus) + - Real-time activity logging with precise timing + +## Critical Test Cases + +### ๐ŸŽฏ **Issue #2861 Reproduction Test** (Most Important) +**Button:** "Efficiency Mode Test" +**Expected:** Window container appears immediately, content loads progressively +**Watch for:** +- Window visible within 100ms of button click +- Content loading message appears initially +- Content completes loading after 2-3 seconds +- No blank or invisible windows + +### โณ **Delayed Content Simulation** +**Button:** "Create Delayed Content Window" +**Expected:** Tests navigation completion timing +**Watch for:** +- Window container appears immediately +- Loading spinner visible initially +- Content loads after 3-second delay +- Window remains visible throughout + +### ๐Ÿ”„ **Hidden โ†’ Show Robustness** +**Button:** "Hidden โ†’ Show Test" +**Expected:** Tests delayed show() calls +**Watch for:** +- Initial response in activity log +- Window appears after exactly 2 seconds +- No timing issues or failures + +## Platform-Specific Testing + +### Windows 10 Pro (Primary Target) +**Enable Efficiency Mode Testing:** +1. Open Task Manager โ†’ Processes tab +2. Find the test application process +3. Right-click โ†’ "Efficiency mode" (if available) +4. Run all test scenarios +5. Verify windows still appear immediately + +**Key Metrics:** +- Window creation: < 100ms +- Content loading: 2-3 seconds +- No invisible windows under any conditions + +### Windows 11 +**Similar to Windows 10 Pro but also test:** +- New Windows 11 efficiency features +- Multiple monitor scenarios +- High DPI scaling + +### macOS +**Focus on consistency:** +- All scenarios should work identical to Windows +- No regressions in existing robust behavior +- Test across different macOS versions if possible + +### Linux +**Test both build variants:** +```bash +# CGO build (default) +wails dev + +# Purego build +CGO_ENABLED=0 wails dev +``` +- Verify both variants behave identically +- Test across different Linux distributions + +## Success Criteria + +### โœ… **Pass Conditions** +- All windows appear within 100ms of button click +- Activity log shows consistent sub-100ms timing +- Content loads progressively without blocking window visibility +- No blank, invisible, or delayed windows under any test scenario +- Efficiency mode (Windows) does not prevent window appearance +- Menu and button testing yield identical results + +### โŒ **Fail Conditions** +- Any window takes >200ms to appear +- Blank or invisible windows under any condition +- Window visibility blocked by content loading +- Efficiency mode prevents window appearance +- Inconsistent behavior between test methods +- Platform-specific failures + +## Reporting Results + +**Please provide this information:** + +``` +Platform: [Windows 10 Pro/Windows 11/macOS/Linux distro + version] +Build Type: [CGO/Purego] (Linux only) +Efficiency Mode: [Enabled/Disabled/N/A] (Windows only) + +Test Results: +- Normal Window: [โœ… Pass / โŒ Fail] - [timing in ms] +- Delayed Content: [โœ… Pass / โŒ Fail] - [container timing / content timing] +- Hiddenโ†’Show: [โœ… Pass / โŒ Fail] - [notes] +- Multiple Windows: [โœ… Pass / โŒ Fail] - [notes] +- Efficiency Mode Test: [โœ… Pass / โŒ Fail] - [critical timing results] + +Notes: +[Any additional observations, error messages, or unexpected behavior] +``` + +## Advanced Testing Scenarios + +### **Rapid Stress Testing** +1. Click "Rapid Creation Test" multiple times quickly +2. Use keyboard shortcuts to rapidly access menu items +3. Create multiple windows then close them rapidly +4. Test system under load (other applications running) + +### **Edge Case Testing** +1. Test during system startup (high load) +2. Test with multiple monitors +3. Test with different DPI scaling settings +4. Test while other WebView2 applications are running + +### **Timing Verification** +1. Use browser dev tools (F12) to check console timing +2. Compare activity log timing with system clock +3. Test on slower/older hardware if available +4. Verify timing consistency across multiple runs + +## Troubleshooting + +### **Common Issues** +- **Blank window**: Check activity log for error messages +- **Slow timing**: Verify system isn't under heavy load +- **Build failures**: Ensure Wails v3 CLI is latest version +- **Import errors**: Run `go mod tidy` in example directory + +### **Debug Information** +The application provides extensive logging: +- Browser console (F12) shows JavaScript timing +- Activity log shows backend call timing +- Go application logs show window creation details +- Check system Task Manager for process efficiency mode status + +This comprehensive testing should validate that the window visibility fixes successfully resolve issue #2861 across all supported platforms. \ No newline at end of file diff --git a/v3/tests/window-visibility-test/assets/index.html b/v3/tests/window-visibility-test/assets/index.html new file mode 100644 index 000000000..22023a5e3 --- /dev/null +++ b/v3/tests/window-visibility-test/assets/index.html @@ -0,0 +1,431 @@ + + + + + + Window Visibility Test - Issue #2861 + + + +
+
+

๐ŸชŸ Window Visibility Test

+

Testing fixes for Wails v3 Issue #2861 - Windows 10 Pro Efficiency Mode

+
+ Current Time: --:--:-- +
+
+ +
+

โœ…Basic Window Tests

+

These tests verify that windows appear immediately when requested, regardless of WebView content loading state.

+
+ + + +
+
+ +
+

โšกStress Tests

+

These tests push the window system to verify robustness under load and timing edge cases.

+
+ + +
+
+ +
+

๐Ÿ”ฌCritical Issue #2861 Test

+

This test specifically targets the Windows efficiency mode bug where WebView2 NavigationCompleted events could be delayed or missed.

+ +
+

Expected Behavior:

+
    +
  • Window container appears immediately upon button click
  • +
  • Content loads progressively (may take 2-3 seconds)
  • +
  • No blank or invisible windows, even under efficiency mode
  • +
+
+ +
+ +
+
+ +
+

๐Ÿ“ŠTest Results & Timing

+

Monitor window creation timing and behavior. Each button click should result in immediate window visibility.

+ +
+

Activity Log

+
+
Ready for testing... Click any button to begin.
+
+
+
+ +
+

๐Ÿ“Testing Instructions

+
+

What to Look For:

+
    +
  • Immediate Window Appearance: Windows should appear within 100ms of clicking
  • +
  • Progressive Loading: Content may load progressively, but window container should be visible immediately
  • +
  • No Efficiency Mode Issues: On Windows, windows should still appear even if Task Manager shows "efficiency mode"
  • +
  • Consistent Behavior: All platforms (Windows, macOS, Linux) should behave similarly
  • +
+
+ +
+

How to Test:

+
    +
  1. Note the current time displayed above
  2. +
  3. Click any test button
  4. +
  5. Immediately observe if a window appears (should be within 100ms)
  6. +
  7. Wait for content to load and check the reported timing
  8. +
  9. Try multiple tests in sequence
  10. +
  11. Test both buttons and menu items
  12. +
+
+
+
+ + + + \ No newline at end of file diff --git a/v3/tests/window-visibility-test/go.mod b/v3/tests/window-visibility-test/go.mod new file mode 100644 index 000000000..04d244468 --- /dev/null +++ b/v3/tests/window-visibility-test/go.mod @@ -0,0 +1,55 @@ +module window-visibility-test + +go 1.24.0 + +toolchain go1.24.4 + +replace github.com/wailsapp/wails/v3 => ../../ + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/lmittmann/tint v1.0.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +// Add any other dependencies that might be needed +// These will be resolved when the user runs go mod tidy diff --git a/v3/tests/window-visibility-test/go.sum b/v3/tests/window-visibility-test/go.sum new file mode 100644 index 000000000..cbadfe003 --- /dev/null +++ b/v3/tests/window-visibility-test/go.sum @@ -0,0 +1,146 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= +github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/tests/window-visibility-test/main.go b/v3/tests/window-visibility-test/main.go new file mode 100644 index 000000000..247c1035c --- /dev/null +++ b/v3/tests/window-visibility-test/main.go @@ -0,0 +1,308 @@ +package main + +import ( + "embed" + "fmt" + "log" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// WindowTestService provides methods for testing window visibility scenarios +type WindowTestService struct { + app *application.App +} + +// NewWindowTestService creates a new window test service +func NewWindowTestService() *WindowTestService { + return &WindowTestService{} +} + +// SetApp sets the application reference (internal method, not exposed to frontend) +func (w *WindowTestService) setApp(app *application.App) { + w.app = app +} + +// CreateNormalWindow creates a standard window - should show immediately +func (w *WindowTestService) CreateNormalWindow() string { + log.Println("Creating normal window...") + + w.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Normal Window - Should Show Immediately", + Width: 600, + Height: 400, + X: 100, + Y: 100, + HTML: "Normal Window

โœ… Normal Window

This window should have appeared immediately after clicking the button.

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

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

โณ Delayed Content Window

+

This window tests navigation completion timing.

+
+

Loading... (simulates heavy content)

+ +

Window container should be visible immediately, even during load.

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

๐Ÿ”„ Hidden Then Show Window

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

Should test the robustness of the show() method.

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

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

๐Ÿ”ข Batch Window %d

+

Part of multiple windows stress test

+

All windows should appear quickly and simultaneously

+

Created at: %s

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

โšก Efficiency Mode Test Window

+

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

+ +
+

Window Container Status

+

โœ… Window container is visible (this text proves it)

+
+ +
+

WebView2 Status

+

โณ WebView2 navigation in progress...

+ +
+ +
+

Heavy Content (simulates Vue.js app)

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

Window Visibility Test

This application tests the fixes for Wails v3 issue #2861

Windows 10 Pro Efficiency Mode Fix

Tests window container vs WebView content visibility


Created for testing robust window visibility patterns

", + }) + }) + + app.Menu.Set(menu) + + // Create main window + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Window Visibility Test - Issue #2861", + Width: 800, + Height: 600, + X: 50, + Y: 50, + URL: "/index.html", + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/wep/README.md b/v3/wep/README.md new file mode 100644 index 000000000..63ea57c98 --- /dev/null +++ b/v3/wep/README.md @@ -0,0 +1,69 @@ +# Wails Enhancement Proposal (WEP) Process + +## Introduction + +Welcome to the Wails Enhancement Proposal (WEP) process. This guide outlines the steps for proposing, discussing, and implementing feature enhancements in Wails. The process is divided into two main parts: + +1. **Submission of Proposal**: This part involves documenting your idea, submitting it for review, and discussing it with the community to gather feedback and refine the proposal. +2. **Implementation of Proposal**: Once a proposal is accepted, the implementation phase begins. This involves developing the feature, submitting a PR for the implementation, and iterating based on feedback until the feature is merged and documented. + +Following this structured approach ensures transparency, community involvement, and efficient enhancement of the Wails project. + +**NOTE**: This process is for proposing new functionality. For bug fixes, documentation improvements, and other minor changes, please follow the standard PR process. + +## Submission of Proposal + +### 1. Idea Initiation + +- **Document Your Idea**: + - Create a new directory: `v3/wep/proposals/` with the name of your proposal. + - Copy the WEP template located in `v3/wep/WEP_TEMPLATE.md` into `v3/wep/proposals//proposal.md`. + - Include any additional resources (images, diagrams, etc.) in the proposal directory. + - Fill in the template with the details of your proposal. Do not remove any sections. + +### 2. Submit Proposal + +- **Open a DRAFT PR**: + - Submit a DRAFT Pull Request (PR) for the proposal with the title `[WEP] `. + - It should only contain the proposal file and any additional resources (images, diagrams, etc.). + - Add a summary of the proposal in the PR description. + +### 3. Community Discussion + +- **Share Your Proposal**: Present your proposal to the Wails community. Try to get support for the proposal to increase the chances of acceptance. If you are on the discord server, create a post in the [`#enhancement-proposals`](https://discord.gg/TA8kbQds95) channel. +- **Gather Feedback**: Refine your proposal based on community input. All feedback should be added as comments in the PR. +- **Show Support**: Agreement with the proposal should be indicated by adding a thumbs-up emoji to the PR. The more thumbs-up emojis, the more likely the proposal will be accepted. +- **Iterate**: Make changes to the proposal based on feedback. +- **Agree on an Implementor**: To avoid stagnant proposals, we require someone agree to implement it. This could be the proposer. +- **Ready for Review**: Once the proposal is ready for review, change the PR status to `Ready for Review`. + +A minimum of 2 weeks should be given for community feedback and discussion. + +### 4. Final Decision + +- **Decision**: The Wails maintainers will make a final decision on the proposal based on community feedback and the proposal's merits. + - If accepted, the proposal will be assigned a WEP number and the PR merged. + - If rejected, the reasons will be provided in the PR comments. + +*NOTE*: If a proposal has not met the required support or has been inactive for more than a month, it may be closed. + +## Implementation of Proposal + +Once a proposal has been accepted and an implementation plan has been decided, the focus shifts to bringing the feature to life. This phase encompasses the actual development, review, and integration of the new feature. Here are the steps involved in the implementation process: + +### 1. Develop the Feature + +- **Follow Standards**: Implement the feature following Wails coding standards. +- **Document the Feature**: Ensure the feature is well-documented during the development process. +- **Submit a PR**: Once implemented, submit a PR for the feature. + +### 2. Feedback and Iteration + +- **Gather Feedback**: Collect feedback from the community. +- **Iterate**: Make improvements based on feedback. + +### 3. Merging + +- **Review of PR**: Address any review comments. +- **Merge**: The PR will be merged after satisfactory review. +The WEP process ensures structured and collaborative enhancement of Wails. Adhere to this guide to contribute effectively to the project's growth. \ No newline at end of file diff --git a/v3/wep/WEP_TEMPLATE.md b/v3/wep/WEP_TEMPLATE.md new file mode 100644 index 000000000..c822c2361 --- /dev/null +++ b/v3/wep/WEP_TEMPLATE.md @@ -0,0 +1,50 @@ +# Wails Enhancement Proposal (WEP) + +## Title + +**Author**: [Your Name] +**Created**: [YYYY-MM-DD] + +## Summary + +Provide a concise summary of the proposal. + +## Motivation + +Explain the problem this proposal aims to solve and why it is necessary. + +## Detailed Design + +Provide a detailed description of the proposed feature, including: + +- Technical details +- Implementation steps +- Potential impact on existing functionality + +## Pros/Cons + +List the pros and cons of the proposed solution. + +## Alternatives Considered + +Discuss alternative solutions or approaches that were considered. + +## Backwards Compatibility + +Address any potential backward compatibility issues. + +## Test Plan + +Outline a testing strategy to ensure the feature works as expected. + +## Reference Implementation + +If applicable, include a link to a reference implementation or prototype. + +## Maintenance Plan + +Describe how the feature will be maintained and supported in the long term. + +## Conclusion + +Summarize the benefits of the proposal and any final considerations. diff --git a/v3/wep/proposals/.gitkeep b/v3/wep/proposals/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/v3/wep/proposals/mobile-support/proposal.md b/v3/wep/proposals/mobile-support/proposal.md new file mode 100644 index 000000000..70e920696 --- /dev/null +++ b/v3/wep/proposals/mobile-support/proposal.md @@ -0,0 +1,332 @@ +# Wails Enhancement Proposal (WEP) + +## Title +Mobile Platform Support for Wails 3 (Android First) + +**Author(s)**: [Your Name] +**Created**: 2024-02-16 + +## Summary + +Add support for mobile platforms (initially Android) to Wails 3, allowing developers to create mobile applications using the same codebase and API patterns as desktop applications. This proposal outlines the approach to extend Wails' existing architecture to support mobile platforms while maintaining API compatibility where possible, using gomobile as the primary build tool. + +## Motivation + +Currently, Wails only supports desktop platforms (Windows, macOS, Linux). Many developers want to target mobile platforms without maintaining separate codebases or learning different frameworks. By adding mobile support to Wails 3: + +1. Developers can leverage their existing Wails knowledge and codebase +2. Applications can share business logic between desktop and mobile versions +3. The Go ecosystem gains another viable option for mobile app development +4. Wails becomes a more comprehensive cross-platform solution +5. CI/CD pipelines can be simplified through gomobile integration + +## Detailed Design + +### Architecture Overview + +The mobile implementation will follow a single-window architecture for several reasons: +1. Aligns with mobile platform conventions +2. Simplifies lifecycle management +3. Reduces complexity in platform-specific code +4. Better user experience on mobile devices + +The implementation will be conditional based on build tags: + +```go +//go:build android +``` + +#### Design Principles + +1. **Maintain API Compatibility** + - All existing APIs should continue to work + - Mobile-specific features should be no-ops on desktop + - Use interfaces for platform-specific implementations + - Leverage existing patterns where possible + +2. **Leverage Existing Systems** + - Use the current event system for lifecycle management + - Keep the asset server architecture + - Maintain service routing functionality + - Use existing dialog API + +3. **Security First** + - Use WebView message handlers over JavascriptInterface + - Maintain existing security patterns + - Safe defaults for mobile permissions + +#### Core Components + +1. **Application Structure** + ```go + type androidPlatform struct { + context *android.Context + webview *android.WebView + activity *android.Activity + parent *App + + msgProcessor *MessageProcessor + + // Atomic flags for state management + isPaused atomic.Bool + isDestroyed atomic.Bool + } + ``` + +2. **Message Processing** + - WebView message handlers preferred over JavascriptInterface for: + * Better security (no reflection-based attacks) + * Better performance (no reflection overhead) + * Better encapsulation + * Consistent with existing message processor pattern + +3. **Event System Integration** + ```go + // Core lifecycle events + const ( + // Application creation and initialization + MobileApplicationCreate = "mobile:ApplicationCreate" + MobileApplicationStart = "mobile:ApplicationStart" + + // Foreground/Background transitions + MobileApplicationResume = "mobile:ApplicationResume" + MobileApplicationPause = "mobile:ApplicationPause" + + // Stopping and destruction + MobileApplicationStop = "mobile:ApplicationStop" + MobileApplicationDestroy = "mobile:ApplicationDestroy" + + // User interaction and system events + MobileBackPressed = "mobile:BackPressed" + MobileLowMemory = "mobile:LowMemory" + MobileNewIntent = "mobile:NewIntent" + + // Configuration changes + MobileConfigChange = "mobile:ConfigChange" + ) + + // Event context data structures + type ApplicationCreateData struct { + SavedInstanceState map[string]interface{} // Restored state if app was killed + Intent *Intent // Launch intent + IsFirstLaunch bool // True if first time launch + } + + type ApplicationResumeData struct { + PauseDuration time.Duration // How long app was paused + IsConfigChange bool // True if resuming from config change + PreviousState string // Previous app state + NetworkStatus string // Current network connectivity + } + + type ApplicationPauseData struct { + IsFinishing bool // True if app is being terminated + IsConfigChange bool // True if pausing for config change + PauseTime time.Time + RemainingMemory int64 // Available system memory + } + + type ApplicationStopData struct { + StopReason string // Why the app is stopping + IsFinishing bool // True if app is being terminated + Duration time.Duration // How long app was running + MemoryUsage int64 // App's memory consumption + } + + type ApplicationDestroyData struct { + IsFinishing bool // True if normal termination + IsConfigChange bool // True if due to config change + StateToSave map[string]interface{} // State to persist + RunDuration time.Duration // Total runtime + } + + type BackPressedData struct { + CanGoBack bool // True if WebView can go back + WebViewHistory int // Number of history entries + TimeSinceLastPress time.Duration // Time since last back press + } + + type LowMemoryData struct { + AvailableMemory int64 // Remaining system memory + Severity string // Warning level: "moderate", "critical" + RecommendedAction string // Suggested action + } + ``` + +4. **Service Support** + - Maintain existing service interfaces: + * ServiceStartup + * ServiceShutdown + * ServiceName + * http.Handler + - Services can hook into mobile events: + ```go + func (s *MyService) ServiceStartup(ctx context.Context, options ServiceOptions) error { + app := application.Get() + app.RegisterHook("mobile:ApplicationPause", func(event *CustomEvent) { + // Handle pause + }) + return nil + } + ``` + +5. **Asset Handling** + - Maintain current asset server architecture + - Keep embed.FS support for consistency + - Use standard URL-based asset requests + - Implement mobile-specific optimizations in assetserver_mobile.go + +6. **Dialog System** + - Use existing dialog API + - Map to Android system dialogs: + * MessageDialog โ†’ AlertDialog + * OpenFileDialog โ†’ Intent.ACTION_OPEN_DOCUMENT + * SaveFileDialog โ†’ Intent.ACTION_CREATE_DOCUMENT + +#### Mobile-Specific Features + +1. **Deep Linking** + ```go + type DeepLinkingOptions struct { + Schemes []string + Hosts []string + IntentFilters []IntentFilter + Handlers DeepLinkHandlers + } + ``` + +2. **Lifecycle Management** + - Emit platform events for all major lifecycle changes + - Allow services to hook into lifecycle events + - Maintain state consistency across the application + +## Pros/Cons + +### Pros +1. Maintains API compatibility with existing applications +2. Leverages existing Wails patterns and systems +3. Security-focused design choices +4. Minimal learning curve for existing Wails developers +5. Flexible service architecture maintained +6. CI/CD friendly through gomobile + +### Cons +1. Single window limitation on mobile +2. Some desktop features may not be available +3. Additional complexity in platform-specific code +4. Performance overhead of WebView vs native UI + +## Alternatives Considered + +The main design decisions that were considered: + +1. **JS Bridge Implementation** + - JavascriptInterface: Rejected due to security concerns and performance + - WebView message handlers: Chosen for security and consistency + - Custom protocol: Rejected as unnecessary complexity + +2. **Asset Handling** + - Android Assets: Considered but adds complexity + - Current embed.FS: Chosen for consistency and simplicity + - Custom asset provider interface: Potential future enhancement + +3. **Dialog Implementation** + - DialogFragment: Rejected as too complex + - System dialogs: Chosen for native feel and simplicity + - Custom dialogs: Unnecessary given requirements + +## Backwards Compatibility + +This proposal maintains backward compatibility by: +1. Not modifying existing APIs +2. Making mobile features no-ops on desktop +3. Using existing patterns and interfaces +4. Maintaining service architecture +5. Keeping current asset handling + +## Test Plan + +Testing focuses on automation with clear goals: + +### Testing Goals +1. Ensure core Wails functionality works correctly +2. Verify mobile-specific features +3. Validate backward compatibility +4. Confirm native integration points + +### Testing Layers + +1. **Unit Tests** + - Go unit tests for non-UI capabilities + - Test mobile message processing + - Test lifecycle management + - Test deep linking handlers + +2. **Integration Tests** + - Automated Espresso tests for Android + - Test communication between Go and WebView + - Test asset loading + - Test application lifecycle + +3. **Automated UI Tests** + - Appium-based testing + - Test navigation flows + - Test platform features + - Test dialog integration + +4. **CI Pipeline Tests** + - Firebase Test Lab integration + - Automated build verification + - Cross-platform compatibility + +## Reference Implementation + +The reference implementation will be done in phases: + +1. Core Platform Integration + - Android Activity setup + - WebView configuration + - Message handler integration + - Basic lifecycle management + +2. Event System Integration + - Mobile event implementation + - Lifecycle event mapping + - Service hooks integration + +3. Asset Server Adaptation + - Mobile-specific optimizations + - URL handling + - Service routing + +4. Dialog Implementation + - System dialog mapping + - Intent handling + - Result processing + +## Maintenance Plan + +1. **Version Support** + - Target Android API 21+ initially + - Regular updates for new Android versions + - Automated testing for version compatibility + +2. **Performance Monitoring** + - Regular performance benchmarking + - Memory usage monitoring + - startup time optimization + +3. **Security Updates** + - Regular security audits + - WebView security patches + - Permission system updates + +## Conclusion + +This proposal provides a path to mobile support while maintaining Wails' core strengths: +1. API compatibility +2. Familiar patterns +3. Strong security +4. Flexible architecture + +The implementation focuses on Android first, with a design that can extend to other mobile platforms in the future. \ No newline at end of file diff --git a/v3/wep/proposals/titlebar-buttons/proposal.md b/v3/wep/proposals/titlebar-buttons/proposal.md new file mode 100644 index 000000000..8bd6b8c4f --- /dev/null +++ b/v3/wep/proposals/titlebar-buttons/proposal.md @@ -0,0 +1,137 @@ +# Wails Enhancement Proposal (WEP) + +## Customising Window Controls in Wails + +**Author**: Lea Anthony +**Created**: 2024-05-20 + +## Summary + +This is a proposal for an API to control the appearance and functionality of window controls in Wails. +This will only be available on Windows and macOS. + +## Motivation + +We currently do not fully support the ability to customise window controls. + +## Detailed Design + +### Controlling Button State + +1. A new enum will be added: + +```go + type ButtonState int + + const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 + ) +``` + +2. These options will be added to the `WebviewWindowOptions` option struct: + +```go + MinimiseButtonState ButtonState + MaximiseButtonState ButtonState + CloseButtonState ButtonState +``` + +3. These options will be removed from the current Windows/Mac options: + +- DisableMinimiseButton +- DisableMaximiseButton +- DisableCloseButton + +4. These methods will be added to the `Window` interface: + +```go + SetMinimizeButtonState(state ButtonState) + SetMaximizeButtonState(state ButtonState) + SetCloseButtonState(state ButtonState) +``` + +The settings translate to the following functionality on each platform: + +| | Windows | Mac | +|-----------------------|------------------------|------------------------| +| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close | +| Hide Min | Disables Min | Hides Min button | +| Hide Max | Disables Max | Hides Max button | +| Hide Close | Hides all controls | Hides Close | + +Note: On Windows, it is not possible to hide the Min/Max buttons individually. +However, disabling both will hide both of the controls and only show the +close button. + +### Controlling Window Style (Windows) + +As Windows currently does not have much in the way of controlling the style of the +titlebar, a new option will be added to the `WebviewWindowOptions` option struct: + +```go + ExStyle int +``` + +If this is set, then the new Window will use the style specified in the `ExStyle` field. + +Example: +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST, + }, + }) + + app.Run() +} +``` + +## Pros/Cons + +### Pros + +- We bring much needed customisation capabilities to both macOS and Windows + +### Cons + +- Windows works slightly different to macOS +- No Linux support (doesn't look like it's possible regardless of the solution) + +## Alternatives Considered + +The alternative is to draw your own titlebar, but this is a lot of work and often doesn't look good. + +## Backwards Compatibility + +This is not backwards compatible as we remove the old "disable button" options. + +## Test Plan + +As part of the implementation, the window example will be updated to test the functionality. + +## Reference Implementation + +There is a reference implementation as part of this proposal. + +## Maintenance Plan + +This feature will be maintained and supported by the Wails developers. + +## Conclusion + +This API would be a leap forward in giving developers greater control over their application window appearances. + diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-v2.6.0/gettingstarted/_category_.json b/website/i18n/ja/docusaurus-plugin-content-docs/version-v2.6.0/gettingstarted/_category_.json deleted file mode 100644 index 597b920df..000000000 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-v2.6.0/gettingstarted/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Getting Started", - "position": 10 -} diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index d1b405813..c5f51220e 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke + ## v2.9.1 - 2024-06-18 ### Fixed